面向对象与原型链
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时,都将直接或间接的获取使用这个对象。