QuickJS JavaScript世界的原始原型对象的建立

面向对象与原型链

JavaScript中,对象的继承是通过原型链的方式来实现。一个子对象可以从父对象中继承其父对象的属性和方法。具体的机制是:当访问一个对象的属性或方法时,首先会查找该对象是否有对应的属性或方法。如果有,就调用其属性和方法。如果没有,就访问其原型对象,查看原型对象中是否存在。如果存在,就调用该属性或方法。如果没有,继续寻找原型对象的原型对象。直到访问到原始原型对象为止。需要注意的是,所有的对象继承自原始原型对象。
在QuickJS中,JS对象反映在引擎中,是一个JSObject结构体。原始原型对象也是一个JSObject结构体。它被保存在JSContext的class_proto中。class_proto是一个数组,保存了所有JavaScript的原始原型对象,如Object、Array、Function等。本文主要研究原始原型对象的建立,其它暂不做深究。
原始对象的创建是在 JS_AddIntrinsicBasicObjects 方法中。使用 JS_NewObjectProto 来创建。

#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .int32 = val }, tag }
JS_TAG_NULL        = 2,
#define JS_NULL      JS_MKVAL(JS_TAG_NULL, 0)

/* Minimum amount of objects to be able to compile code and display
   error messages. No JSAtom should be allocated by this function. */
// 添加固有基础对象
static void JS_AddIntrinsicBasicObjects(JSContext *ctx)
{
    JSValue proto;
    int i;

    ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL);
    ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0,
                                           JS_CFUNC_generic, 0,
                                           ctx->class_proto[JS_CLASS_OBJECT]);
    ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = JS_DupValue(ctx, ctx->function_proto);
    ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject(ctx);
#if 0
    /* these are auto-initialized from js_error_proto_funcs,
       but delaying might be a problem */
    JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_name,
                           JS_AtomToString(ctx, JS_ATOM_Error),
                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
    JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_message,
                           JS_AtomToString(ctx, JS_ATOM_empty_string),
                           JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
#endif
    JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ERROR],
                               js_error_proto_funcs,
                               countof(js_error_proto_funcs));

    for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
        proto = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ERROR]);
        JS_DefinePropertyValue(ctx, proto, JS_ATOM_name,
                               JS_NewAtomString(ctx, native_error_name[i]),
                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
        JS_DefinePropertyValue(ctx, proto, JS_ATOM_message,
                               JS_AtomToString(ctx, JS_ATOM_empty_string),
                               JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
        ctx->native_error_proto[i] = proto;
    }

    /* the array prototype is an array */
    ctx->class_proto[JS_CLASS_ARRAY] =
        JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT],
                               JS_CLASS_ARRAY);

    ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]),
                                     JS_PROP_INITIAL_HASH_SIZE, 1);
    add_shape_property(ctx, &ctx->array_shape, NULL,
                       JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH);

    /* XXX: could test it on first context creation to ensure that no
       new atoms are created in JS_AddIntrinsicBasicObjects(). It is
       necessary to avoid useless renumbering of atoms after
       JS_EvalBinary() if it is done just after
       JS_AddIntrinsicBasicObjects(). */
    //    assert(ctx->rt->atom_count == JS_ATOM_END);
}

JS_NewObjectProto

JS_NewObjectProto 方法用于根据原型来创建对象。

JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto)
{
    return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT);
}

/* WARNING: proto must be an object or JS_NULL */
JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val,
                               JSClassID class_id)
{
    JSShape *sh;
    JSObject *proto;
    //1. 根据传入的 JSValue 获取 JSObject - 原型对象
    // 当前场景下返回的proto是NULL
    proto = get_proto_obj(proto_val);
    //2. 据原型对象获取 shape
    sh = find_hashed_shape_proto(ctx->rt, proto);
    if (likely(sh)) {
        // 如果shape存在,就直接使用已经有的shape
        sh = js_dup_shape(sh);
    } else {
        // 如果不存在,就创建一个新的shape
        // 当前场景下,创建的JSShape的proto值为NULL
        sh = js_new_shape(ctx, proto);
        if (!sh)
            return JS_EXCEPTION;
    }
    //3. 根据shape创建对象
    return JS_NewObjectFromShape(ctx, sh, class_id);
}


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));
    if (unlikely(!p))
        goto fail;
    // 设置对象的class id
    p->class_id = class_id;
    // 
    p->extensible = TRUE;
    // 释放标识 only used when freeing objects with cycles 
    p->free_mark = 0;
    p->is_exotic = 0;
    p->fast_array = 0;
    p->is_constructor = 0;
    p->is_uncatchable_error = 0;
    p->tmp_mark = 0;
    p->is_HTMLDDA = 0;
    p->first_weak_ref = NULL;
    p->u.opaque = NULL;
    // 设置对象的shape
    p->shape = sh;
    // 根据shape,为对象的属性值申请内存(用于存储对象属性具体的值和setter getter)
    p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
    // 确保对象属性内存分配成功
    if (unlikely(!p->prop)) {
        js_free(ctx, p);
    fail:
        js_free_shape(ctx->rt, sh);
        return JS_EXCEPTION;
    }

    //根据对象的class_id,分别进行一些相关类型的初始化
    switch(class_id) {
    case JS_CLASS_OBJECT:
        break;
    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;
    case JS_CLASS_C_FUNCTION:
        p->prop[0].u.value = JS_UNDEFINED;
        break;
    case JS_CLASS_ARGUMENTS:
    case JS_CLASS_UINT8C_ARRAY:
    case JS_CLASS_INT8_ARRAY:
    case JS_CLASS_UINT8_ARRAY:
    case JS_CLASS_INT16_ARRAY:
    case JS_CLASS_UINT16_ARRAY:
    case JS_CLASS_INT32_ARRAY:
    case JS_CLASS_UINT32_ARRAY:
#ifdef CONFIG_BIGNUM
    case JS_CLASS_BIG_INT64_ARRAY:
    case JS_CLASS_BIG_UINT64_ARRAY:
#endif
    case JS_CLASS_FLOAT32_ARRAY:
    case JS_CLASS_FLOAT64_ARRAY:
        p->is_exotic = 1;
        p->fast_array = 1;
        p->u.array.u.ptr = NULL;
        p->u.array.count = 0;
        break;
    case JS_CLASS_DATAVIEW:
        p->u.array.u.ptr = NULL;
        p->u.array.count = 0;
        break;
    case JS_CLASS_NUMBER:
    case JS_CLASS_STRING:
    case JS_CLASS_BOOLEAN:
    case JS_CLASS_SYMBOL:
    case JS_CLASS_DATE:
#ifdef CONFIG_BIGNUM
    case JS_CLASS_BIG_INT:
    case JS_CLASS_BIG_FLOAT:
    case JS_CLASS_BIG_DECIMAL:
#endif
        p->u.object_data = JS_UNDEFINED;
        goto set_exotic;
    case JS_CLASS_REGEXP:
        p->u.regexp.pattern = NULL;
        p->u.regexp.bytecode = NULL;
        goto set_exotic;
    default:
    set_exotic:
        if (ctx->rt->class_array[class_id].exotic) {
            p->is_exotic = 1;
        }
        break;
    }
    // 为对象设置引用计数
    p->header.ref_count = 1;
    // 将这个对象加入到回收监控中
    add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT);
    return JS_MKPTR(JS_TAG_OBJECT, p);
}

#define JS_VALUE_GET_PTR(v) ((v).u.ptr)
#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag)

// 获取JSValue的原型对象
static JSObject *get_proto_obj(JSValueConst proto_val)
{
    // 判单JSValue是否是Object,如果不是,直接返回NULL
    if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT)
        return NULL;
    else
        //根据 proto_val 获取它的对象 
        return JS_VALUE_GET_OBJ(proto_val);
}

/* find a hashed empty shape matching the prototype. Return NULL if
   not found */
//根据 JSObject 获取它的 JSShape
static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto)
{
    JSShape *sh1;
    uint32_t h, h1;

    h = shape_initial_hash(proto);
    h1 = get_shape_hash(h, rt->shape_hash_bits);
    for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) {
        if (sh1->hash == h &&
            sh1->proto == proto &&
            sh1->prop_count == 0) {
            return sh1;
        }
    }
    return NULL;
}

// 将申请的内存强转为JSShape
static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size)
{
    return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size);
}

/* create a new empty shape with prototype 'proto' */
static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto,
                                        int hash_size, int prop_size)
{
    JSRuntime *rt = ctx->rt;
    void *sh_alloc;
    JSShape *sh;

    /* resize the shape hash table if necessary */
    if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) {
        resize_shape_hash(rt, rt->shape_hash_bits + 1);
    }

    sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size));
    if (!sh_alloc)
        return NULL;
    sh = get_shape_from_alloc(sh_alloc, hash_size);
    sh->header.ref_count = 1;
    add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
    if (proto)
        JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto));
    sh->proto = proto;
    memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) *
           hash_size);
    sh->prop_hash_mask = hash_size - 1;
    sh->prop_size = prop_size;
    sh->prop_count = 0;
    sh->deleted_prop_count = 0;
    
    /* insert in the hash table */
    // 设置JSShape的hash
    sh->hash = shape_initial_hash(proto);
    sh->is_hashed = TRUE;
    sh->has_small_array_index = FALSE;
    js_shape_hash_link(ctx->rt, sh);
    return sh;
}
/* 
    1. 如果需要调大装载shape的hash的表
    2. 为新shape分配内存
    3. 将申请的内存转化为JSShape
    4. 设置引用计数和回收
    5. 为JSShape设置它的原型对象
    6. 将JSShape的hash段清空
    7. 设置JSShape的属性数量和大小
    8. 设置JSShape的hash
*/

#define JS_PROP_INITIAL_SIZE 2
#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */
static JSShape *js_new_shape(JSContext *ctx, JSObject *proto)
{
    return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
                         JS_PROP_INITIAL_SIZE);
}


/* same magic hash multiplier as the Linux kernel */
static uint32_t shape_hash(uint32_t h, uint32_t val)
{
    return (h + val) * 0x9e370001;
}

/* truncate the shape hash to 'hash_bits' bits */
static uint32_t get_shape_hash(uint32_t h, int hash_bits)
{
    return h >> (32 - hash_bits);
}

/** 获取shape的hash */
static uint32_t shape_initial_hash(JSObject *proto)
{
    uint32_t h;
    h = shape_hash(1, (uintptr_t)proto);
    if (sizeof(proto) > 4)
        h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32);
    return h;
}

JSObject是Javascript代码中的对象在QuickJS引擎中的实现。原始原型对象是使用 JS_NewObjectProto 来创建。JS_NewObjectProto 调用了 JS_NewObjectProtoClass 函数实现创建过程。

 //1. 使用JS_NewObjectProto创建原始原型对象,参数是ctx和JS_NULL
 ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL);
 
 //2. 调用JS_NewObjectProtoClass完成原始原型对象的创建
 JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT)

调用时,我们传入了第三个参数,分别是ctx,JS_NULL 和 JS_CLASS_OBJECT 。
JS_NewObjectProtoClass 中分三步创建原型对象。
第一步,根据传入的 proto(此处为JS_NULL) 获取它的原型对象。根据其调用的 get_proto_obj 方法,可知,当JSValue不是JSObject时,返回的是NULL。
第二步,根据获取的原型对象,获取其JSShape。使用 find_hashed_shape_proto 方法来获取。如果shape不存在,就根据原型对象,创建一个新的JSShape。在当前场景中,proto是NULL,JSShape也同样不存在,因此是根据其原型对象创建了新的JSShape。根据创建函数 js_new_shape2 来源码来看,该JSShape的原型对象是NULL。
第三步,根据 JSShape 创建原型对象。此处,将新创建的JSObject的shape设置为第二步获取的JSShape。
据此可知,JSObject持有JSShape,而JSShape是根据原型对象—JSObject 创建而来的。他们并不是一个环形互相引用关系,是一个链表的第次持有关系。
最后,将刚刚创建的原型对象放入 ctx->class_proto[JS_CLASS_OBJECT] 中。后续JavaScript环境中,创建JSObject时,都将直接或间接的获取使用这个对象。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值