孫向暉 sunshineormer at hotmail.com
在正式開始之前,先說點關於RHG的話題,我的JHG系列,基本是在仿照着RHG的風格在寫作。RHG的述事風格很有條理性,並且會指導你跳過跳過再跳過一些無關痛癢的程序片段。這是對的,在一大堆程序中,能夠快速準確的定位,不是件很容易的事情。
從今天開始,我將講述我對蜘蛛猴源代碼的查找過程:
在js.c 找到main裏面的調用過程,注意,下面的函數都是精簡過的,而且是平級的關係:
rt = JS_NewRuntime(...
cx = JS_NewContext(rt,...
glob = JS_NewObject(cx, &global_class, NULL, NULL);
JS_InitStandardClasses(cx, glob)
JS_DefineFunctions(cx, glob, shell_functions)
ok,現在來看一下,運行時和上下文環境是解釋引擎的初始化,可以先跳過,哈,我們真正關心的肯定是從glob開始的那3句了:glob是ECMA262中要求的內置全局對象,這裏有我們比較感興趣的NewObject方法;緊接其後的,將是對其他內置對象的創建過程(JS_InitStandardClasses);最後,我們可以看看全局對象global都有哪些方法。
JS_NewObject的函數聲明,被定義在jsobj.h中,函數定義在jsobj.c中:
:
extern JSObject *
js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent);
記住,因爲是要初始化glob對象,我們在傳遞參數是,proto和parent都傳遞了NULL。這樣,有效傳遞到函數體中的,就只有剛剛創建的上下文環境(cx)和clasp了。clasp的值被賦予了global_class,這是個全局變量,定義在js.c中
JSClass global_class = {
"global", JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
global_enumerate, (JSResolveOp) global_resolve,
JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
在這個變量中,我們發現了“global”這個單詞,結合JSClass這名字,再根據MFC等框架的經驗,很容易的我們就可以聯想到,這是用來做動態生成和RTTI的類名。
要想看懂這個結構體,一點也不難,只需到jsapi.h中找JSClass的定義即可。裏面也沒有什麼太多的玩意,只是有一堆的函數指針,我們只需簡單留言其中的強制非空部分即可。
struct JSClass {
const char *name;
uint32 flags;
/* Mandatory non-null function pointer members. */
JSPropertyOp addProperty;
JSPropertyOp delProperty;
JSPropertyOp getProperty;
JSPropertyOp setProperty;
JSEnumerateOp enumerate;
JSResolveOp resolve;
JSConvertOp convert;
JSFinalizeOp finalize;
/* Optionally non-null members start here. */
JSGetObjectOps getObjectOps;
JSCheckAccessOp checkAccess;
JSNative call;
JSNative construct;
JSXDRObjectOp xdrObject;
JSHasInstanceOp hasInstance;
JSMarkOp mark;
JSReserveSlotsOp reserveSlots;
};
放眼望去,偶的老天,原來類的屬性操作(增加屬性,刪除屬性,查詢和設置屬性都是在這定義的啊)。要知道,在javascript中,所有的attributes和方法都是屬性啊。按下不表。
還是跳回到newObject
/* Bootstrap the ur-object, and make it the default prototype object. */
if (!proto) {
if (!js_GetClassId(cx, clasp, &id))
return NULL;
if (!js_GetClassPrototype(cx, parent, id, &proto))
return NULL;
if (!proto &&
!js_GetClassPrototype(cx, parent, INT_TO_JSID(JSProto_Object),
&proto)) {
return NULL;
}
}
先看這段代碼,簡而言之,想做的事情是,找到要創建對象的原型,如果實在找不到,就使用缺省的原型對象。跳過。
接下來的代碼,
/* Always call the class's getObjectOps hook if it has one. */
ops = clasp->getObjectOps
? clasp->getObjectOps(cx, clasp)
: &js_ObjectOps;
只是想找一個對象的操作方法集,因爲我們在定義global_class全局對象時,使用了JSCLASS_NO_OPTIONAL_MEMBERS宏,可以很輕鬆的判定這個表達式如同:
ops = &js_ObjectOps;
繼續向後:
/*
* Allocate a zeroed object from the GC heap. Do this *after* any other
* GC-thing allocations under js_GetClassPrototype or clasp->getObjectOps,
* to avoid displacing the newborn root for obj.
*/
obj = (JSObject *) js_NewGCThing(cx, GCX_OBJECT, sizeof(JSObject));
if (!obj)
return NULL;
從註釋中,我們可以輕鬆的看出,按照ECMAScript的說法,對象被分配到heap中,已經得到了實現了。
被定義在了jsgc.c中,我們不想太多的去看堆的alloc分配,只要知道這裏生成了一個空的JSObject對象即可。
void *
js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes)
再接着,是一大段的判斷條件,想法很簡單,既而是已經在heap中創建了對象,就要給對象的map和slot進行賦值了:一種做法是判斷了若干條件後,用原型的map;另一種可能,是創建新的map。
你會問map裏面都存放了什麼?呵呵。好問題。且聽下回分解吧。