论坛首页 Web前端技术论坛

[领域]Javascript Hacking Guide 系列文章 (2007-2-7更新到part6)

浏览 8928 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-02-06  

那天听东东将javascript,他讲得也匆忙,我听得也犯晕,心想如果能够建立起javascript的内存对象模型该多好啊。可是在网上找了好久,也没有发现javascript的Hacking guide,不经意间,却找到了ruby hacking guide,不错不错。还好有mozilla 中蜘蛛猴的源代码,我想离真正建立javascript的对象模型(内存模型)已经不远了。

仿照ruby hacking guide的做法,先把已经掌握的资料用图表示出来,关于其之间的详细的关系,我会在后面的文章中举例说明。

 

图示说明:椭圆边框的部分是c的结构体(struct),带圆点的线条代表指针,在椭圆外的是该结构体所在的文件。

这算javascript内存模型的第一部分吧。老是等研究完了再写,恐怕就没有激情了。呵呵

 
   发表时间:2007-02-06  
请将这一系列的文章发在同一topic下,谢谢。
0 请登录后投票
   发表时间:2007-02-06  
sorry,我写blog写惯了,喜欢一段一段的写。马上改正。
0 请登录后投票
   发表时间:2007-02-06  

part2:

感谢超德和梁健,你们是我前进的动力。

昨天贴出来的,只能算是初步的编程模型,如果想弄清楚javascript的内存模型,必须看这幅图,不过这幅图还有一些简化的部分。以后会用程序来验证和说明这幅图的正确性,敬请期待。

 part3:

跟东、雪和健讨论后,重新修改了图2,可以联系图1来思考它们之间的演进关系了。

这个周末,基本上是跟指针,对象一起度过的。即使是在我偶而为之的睡梦中,也是在思考函数指针和指针函数。呵呵。

周日整理出来的继承(原型)链图,可以说是javascript对象基础中比较深的部分了。

浅蓝色的,typeof() ==Object, 灰色的部分,typeof == Function。这里面只有FunctionPrototype出乎我的意料,我对它的期望值是浅蓝色,可是,运行程序的结果证明,它是灰色调的。

 

0 请登录后投票
   发表时间:2007-02-06  

part4 :

在正式开始之前,先说点关于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)

<o:p> </o:p>

ok,现在来看一下,运行时和上下文环境是解释引擎的初始化,可以先跳过,哈,我们真正关心的肯定是从glob开始的那3句了:globECMA262中要求的内置全局对象,这里有我们比较感兴趣的NewObject方法;紧接其后的,将是对其他内置对象的创建过程(JS_InitStandardClasses;最后,我们可以看看全局对象global都有哪些方法。

<o:p> </o:p>

JS_NewObject的函数声明,被定义在jsobj.h中,函数定义在jsobj.c中:

extern JSObject *

js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent);

记住,因为是要初始化glob对象,我们在传递参数是,protoparent都传递了NULL。这样,有效传递到函数体中的,就只有刚刚创建的上下文环境(cx)和clasp了。clasp的值被赋予了global_class,这是个全局变量,定义在js.c

<o:p> </o:p>

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

};

<o:p> </o:p>

在这个变量中,我们发现了“global”这个单词,结合JSClass这名字,再根据MFC等框架的经验,很容易的我们就可以联想到,这是用来做动态生成和RTTI的类名。

要想看懂这个结构体,一点也不难,只需到jsapi.h中找JSClass的定义即可。里面也没有什么太多的玩意,只是有一堆的函数指针,我们只需简单留言其中的强制非空部分即可。

<o:p> </o:p>

struct JSClass {

    const char          *name;

    uint32              flags;

<o:p> </o:p>

    /* Mandatory non-null function pointer members. */

    JSPropertyOp        addProperty;

    JSPropertyOp        delProperty;

    JSPropertyOp        getProperty;

    JSPropertyOp        setProperty;

    JSEnumerateOp       enumerate;

    JSResolveOp         resolve;

    JSConvertOp         convert;

    JSFinalizeOp        finalize;

<o:p> </o:p>

    /* 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

<o:p> </o:p>

/* 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;

<o:p> </o:p>

继续向后:

/*

     * 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中创建了对象,就要给对象的mapslot进行赋值了:一种做法是判断了若干条件后,用原型的map;另一种可能,是创建新的map

<o:p> </o:p>

你会问map里面都存放了什么?呵呵。好问题。且听下回分解吧。

 

0 请登录后投票
   发表时间:2007-02-06  

part5:

mapJSObject的一个重要属性,存放一个对象的所有的属性的入口。要想了解map,就需要打开jsobj.h文件,看里面的定义。

struct JSObject {

    JSObjectMap *map;

    jsval       *slots;

};

<o:p> </o:p>

很自然的,我们还要找到JSObjectMap的定义,它也在jsobj.h文件中

struct JSObjectMap {

    jsrefcount  nrefs;          /* count of all referencing objects */

    JSObjectOps *ops;           /* high level object operation vtable */

    uint32      nslots;         /* length of obj->slots vector */

    uint32      freeslot;       /* index of next free obj->slots element */

};

在这个定义中,最重要的信息隐含在注释中,第一个是对ops的注释,我们会看到ops其实是一个virtual table。这表示,对象的所有的属性名,都存放在这个vtable中;第2个信息是对nslots的注释,它提及obj->slots其实是一个vector

这如同在告诉我们,我们在定义一个对象的属性时,属性的名称和类型等信息,会通过ops增加到属性表中,属性的值会对应放到slots这个vector中。例如:

var obj = new Object();

obj.a = 5;

 那么“a”这个属性的类型是整型的,slots中增加了个新的值5。仔细看去,opsJSObjectOps这个结构体中,我们可以在jsapi.h中找到这个结构体的定义:

struct JSObjectOps {

    /* Mandatory non-null function pointer members. */

    JSNewObjectMapOp    newObjectMap;

    JSObjectMapOp       destroyObjectMap;

    JSLookupPropOp      lookupProperty;

    JSDefinePropOp      defineProperty;

    JSPropertyIdOp      getProperty;<o:p></o:p>

    JSPropertyIdOp      setProperty;

    JSAttributesOp      getAttributes;

    JSAttributesOp      setAttributes;

    JSPropertyIdOp      deleteProperty;

    JSConvertOp         defaultValue;

    JSNewEnumerateOp    enumerate;

    JSCheckAccessIdOp   checkAccess;

<o:p> </o:p>

    /* Optionally non-null members start here. */

    JSObjectOp          thisObject;

    JSPropertyRefOp     dropProperty;

    JSNative            call;

    JSNative            construct;

    JSXDRObjectOp       xdrObject;

    JSHasInstanceOp     hasInstance;

    JSSetObjectSlotOp   setProto;

    JSSetObjectSlotOp   setParent;

    JSMarkOp            mark;

    JSFinalizeOp        clear;

    JSGetRequiredSlotOp getRequiredSlot;

    JSSetRequiredSlotOp setRequiredSlot;

};

<o:p> </o:p>

我们可以简单的浏览一下其中的函数指针名称,创建objectmap,定义属性,删除属性,给属性设置值或者获取属性值。。。。闭上眼睛想一想,这其中的函数指针是如此之多,所以一个对象的属性的类型判断(比如是整型还是String还是其他的)或者是属性名或者属性值的存放,都是通过预先定义好的指针函数来完成。

我们甚至可以用一个新的示意图来表示这一切:

<o:p> </o:p>

<v:shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype><v:shape id="_x0000_i1025" style="WIDTH: 213.75pt; HEIGHT: 131.25pt" type="#_x0000_t75"><v:imagedata o:title="" src="file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\msohtml1\01\clip_image001.png"></v:imagedata></v:shape>

我们可以这样去想,有一个table,里面存放对象的属性列表。根据ruby hacking guide的说法,我们可以想像,有一张大的表,存放所有的对象的属性,也可能每个对象有自己的属性表。但,这属于具体实现的范畴,我们只要明白每个对象有属于自己的属性表,对表格中的行或者列进行操作时,是由JSObjectOps这个结构体中的指针函数来完成的就可以了。

/*

     * Share proto's map only if it has the same JSObjectOps, and only if

     * proto's class has the same private and reserved slots as obj's map

     * and class have.  We assume that if prototype and object are of the

     * same class, they always have the same number of computed reserved

     * slots (returned via clasp->reserveSlots); otherwise, prototype and

     * object classes must have the same (null or not) reserveSlots hook.

     */

    if (proto &&

        (map = proto->map)->ops == ops &&

        ((protoclasp = OBJ_GET_CLASS(cx, proto)) == clasp ||

         (!((protoclasp->flags ^ clasp->flags) &

            (JSCLASS_HAS_PRIVATE |

             (JSCLASS_RESERVED_SLOTS_MASK << JSCLASS_RESERVED_SLOTS_SHIFT))) &&

          protoclasp->reserveSlots == clasp->reserveSlots)))

    {

        /*

         * Default parent to the parent of the prototype, which was set from

         * the parent of the prototype's constructor.

         */

        if (!parent)

            parent = OBJ_GET_PARENT(cx, proto);

<o:p> </o:p>

        /* Share the given prototype's map. */

        obj->map = js_HoldObjectMap(cx, map);

<o:p> </o:p>

        /* Ensure that obj starts with the minimum slots for clasp. */

        nslots = JS_INITIAL_NSLOTS;

    } else {

        /* Leave parent alone.  Allocate a new map for obj. */

        map = ops->newObjectMap(cx, 1, ops, clasp, obj);

        if (!map)

            goto bad;

        obj->map = map;

<o:p> </o:p>

        /* Let ops->newObjectMap set nslots so as to reserve slots. */

        nslots = map->nslots;

    }

Ok,我们在上回书说到有若干的判断条件,就是为了创建一个map,并设置为obj->map的属性――不管是用parent的原型来创建,还是单独创建新的mapif 。。。else 部分),最终都是为了这个目的。

<o:p> </o:p>

/* Allocate a slots vector, with a -1'st element telling its length. */

    newslots = AllocSlots(cx, NULL, nslots);

    if (!newslots) {

        js_DropObjectMap(cx, obj->map, obj);

        obj->map = NULL;

        goto bad;

    }

<o:p> </o:p>

    /* Set the proto, parent, and class properties. */

    newslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto);

    newslots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(parent);

    newslots[JSSLOT_CLASS] = PRIVATE_TO_JSVAL(clasp);

<o:p> </o:p>

    /* Clear above JSSLOT_CLASS so the GC doesn't load uninitialized memory. */

    for (i = JSSLOT_CLASS + 1; i < nslots; i++)

        newslots[i] = JSVAL_VOID;

0 请登录后投票
   发表时间:2007-02-06  
预告内容: 在接下来的文章中,将对global全局对象进行详细讲解,并给大家分析创建函数的过程。根据这部分内容,将对前面的图片进行部分的修改。
0 请登录后投票
   发表时间:2007-02-06  
typeof function ==object?

很好的文章,让不懂c系列的我感觉很有收获,回头再好好学学c系列的语言。
0 请登录后投票
   发表时间:2007-02-06  
typeof Function == function。(图中灰的表示typeof的返回值是function)
0 请登录后投票
   发表时间:2007-02-06  
系统地建立JS知识和对其原理的理解 ,这是一个图文并茂的好材料
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics