当前位置: 首页 > article >正文

【js引擎】quickjs 中的两个 proto

别被 quickjs 的接口命名骗了,接口 JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto); 中的 proto 并不是 obj.__proto__,而是设计模式中的原型模式。

了解 javascript 语言的面向对象编程模式的同学都知道,javascript 中的继承和多态是使用原型链来实现的。见官方描述: ECMAScript® 2023 Language Specification 4.3.1

javascript 的原型链( prototype chain)

总结来说,每一个构造函数都有一个prototype属性(property)。当使用 new 表达式执行构造函数时,返回的对象会包含一个指向这个构造函数的prototype属性的引用,即:__proto__。所以 prototype 属性是共享的,所有通过该构造函数创建的对象,都会指向同一个 prototype 属性。
prototype 属性本身也是一个对象,也有 __proto__ 引用,这个逐次向上的引用链,叫做原型链。

mdn 和 阮一峰的日志中,都详细描述过该机制,并且配有样例,这里不再多说。

quickjs 中的原型链(prototype chain) 和 原型模式(Prototype Pattern)

原型模式

quickjs 是开发 javascript 运行时最经常使用的 js 引擎之一。quickjs 引擎使用 c 语言开发。在使用 quickjs 引擎的时候,其中创建对象的接口,很容易让人迷惑:

JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto);

我们会想当然的认为 proto 就是 javascript 中的 __proto__引用。我们会认为

JSValue obj = JS_NewObjectProto(ctx, my_proto);

创建的 object 满足 obj.__proto__ is my_proto。而实际上并不是。要说清楚这个问题,我们还需要结合另外几个接口来看

JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto, JSClassID class_id);
JSValue JS_NewObjectClass(JSContext *ctx, int class_id);
JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto);
JSValue JS_NewObject(JSContext *ctx);

在这里插入图片描述
结合这张这四个函数的调用关系图,可以得到以下结论:

  1. 创建一个对象与两件事有关,proto 对象 和 class id
  2. 当没有class id 时,默认class id 为 JS_CLASS_OBJECT
  3. 当没有 proto 时,会从 context->class_proto 获取一个 proto。

那这两个变量到底有什么作用呢?继续向下追溯发现,proto 的作用是找到其背后的 shape 对象。然后传递给 JS_NewObjectFromShape 函数,而 class_id 的作用暂时还没提现出来。

JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val,
                               JSClassID class_id)
{
    JSShape *sh;
    JSObject *proto;

    proto = get_proto_obj(proto_val);
    sh = find_hashed_shape_proto(ctx->rt, proto);
    if (likely(sh)) {
        sh = js_dup_shape(sh);
    } else {
        sh = js_new_shape(ctx, proto);
        if (!sh)
            return JS_EXCEPTION;
    }
    return JS_NewObjectFromShape(ctx, sh, class_id);
}

再继续看JS_NewObjectFromShape 函数,会发现shape在这里只是被设置进去了,而 class_id 的作用是,用来执行一些类型独有的设置。例如 ARRAY、C_FUNCTION 等。而如果没有预定义的特殊设置,则只会关注该对象是否是 exotic 的。

static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id)
{
    JSObject *p;

    js_trigger_gc(ctx->rt, sizeof(JSObject));
    p = js_malloc(ctx, sizeof(JSObject));
	....
    p->shape = sh;
	....
    switch(class_id) {
    .....
    case JS_CLASS_ARRAY:
        {
            JSProperty *pr;
            p->is_exotic = 1;
            p->fast_array = 1;
            p->u.array.u.values = NULL;
            p->u.array.count = 0;
            p->u.array.u1.size = 0;
            /* the length property is always the first one */
            if (likely(sh == ctx->array_shape)) {
                pr = &p->prop[0];
            } else {
                /* only used for the first array */
                /* cannot fail */
                pr = add_property(ctx, p, JS_ATOM_length,
                                  JS_PROP_WRITABLE | JS_PROP_LENGTH);
            }
            pr->u.value = JS_NewInt32(ctx, 0);
        }
        break;
		.....
    default:
    set_exotic:
        if (ctx->rt->class_array[class_id].exotic) {
            p->is_exotic = 1;
        }
        break;
    }
	....
    return JS_MKPTR(JS_TAG_OBJECT, p);
}

所以总结一下,proto 的作用,是 copy 其背后的 shape,而 class_id 的作用,是做一些特殊类型的独有配置。因此这里的 proto 的含义其实是设计模式中的原型模式。通过已有的原型对象,快速生成与原型对象相同的实例。而具体shape是什么作用,以后有机会再单说。

多说一句,class_id 并不是用来判断 object 的类型的。有些同学看到switch case 中判断类型的操作,就以为 class_Id 是用来判断类型的。实际上,只要有class_id 的对象,都是 Object。至于javascript定义的 7 种值类型:Undefined、Null、Boolean、String、Symbol、Numeric、Object,是靠JSValue 中的 Tag 来区分的。

原型链

那么原型链是如何实现的呢?我们只需要找一下 quickjs 是如何查找 __proto__ 属性的就好了。
__proto__属性是object类都有的属性,统一定义在 js_object_proto_funcs 中,可以看到 __proto__在quickjs中,实际上是 shape 中的一个字段

static const JSCFunctionListEntry js_object_proto_funcs[] = {
	....
    JS_CGETSET_DEF("__proto__", js_object_get___proto__, js_object_set___proto__ ),
	....
};

static JSValue js_object_get___proto__(JSContext *ctx, JSValueConst this_val)
{
	...
    ret = JS_GetPrototype(ctx, val);
    ...
    return ret;
}
JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj)
{
    JSValue val;
	.....
            p = p->shape->proto;
            if (!p)
                val = JS_NULL;
            else
                val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p));
        }
	.....
    return val;
}

http://www.kler.cn/a/467938.html

相关文章:

  • UCAS-算法设计与分析(专硕)-复习参考
  • Timer、Ticker使用及其注意事项
  • 基于物联网的冻保鲜运输智能控制系统
  • C++:位与运算符
  • Go语言的数据类型
  • 《探秘计算机视觉与深度学习:开启智能视觉新时代》
  • 5 Linux 网络编程基础 API
  • 家教老师预约平台小程序系统开发方案
  • 数据结构-顺序表及其应用
  • 【pytorch练习】使用pytorch神经网络架构拟合余弦曲线
  • 电商项目-基于ElasticSearch实现商品搜索功能(一)
  • 2025-01-04 Unity插件 YodaSheet1 —— 插件介绍
  • 【深度学习入门_基础篇】线性代数本质
  • 进军AI大模型-Langchain程序部署
  • DS复习提纲模版
  • asp.net core 发布到iis后,一直500.19,IIS设置没问题,安装了sdk,文件夹权限都有,还是报错
  • RestClient操作Elasticsearch
  • 【Java】集合中的List【主线学习笔记】
  • 蓝色简洁引导页网站源码
  • 我们公司只有3个人,一个前端,一个后端
  • Java:基于springboot的高校实习管理系统的设计和开发
  • 浅谈棋牌游戏开发流程二:后端技术选型与基础环境搭建
  • 【SPIE独立出版,首届会议见刊后27天EI检索!高录用】第二届遥感、测绘与图像处理国际学术会议(RSMIP 2025)
  • 数据库高安全—角色权限:角色创建角色管理
  • 永磁同步电机预测模型控制(MPC)
  • 计算机网络 —— 网络编程(套接字深度理解)