Dojo


dojo的类机制支持类声明、继承、调用父类方法等功能。dojo在底层实现上是通过操作原型链来实现其类机制的,而在实现继承时采用类式继承的方式。值得一提的是,dojo的类机制允许进行多重继承(注意,只有父类列表中的第一个作为真正的父类,其它的都是将其属性以mixin的方法加入到子类的原型链中),为解决多重继承时类方法的顺序问题,dojo用JavaScript实现了Python和其它多继承语言所支持的C3父类线性化算法,以实现线性的继承关系,想了解更多该算法的知识,可参考这里,我们在后面的分析中将会简单讲解dojo对此算法的实现。

1.dojo类声明概览

dojo类声明相关的代码位于“/dojo/_base/declare.js”文件中,定义类是通过dojo.declare方法来实现的。关于这个方法的基本用法,已经在dojo类机制简介这篇文章中进行了阐述,现在我们看一下它的实现原理(在这部分的代码分析中,会在整体上介绍dojo如何声明类,后文会对里面的重要细节内容进行介绍):

 
//此即为dojo.declare方法的定义
d.declare = function(className, superclass, props){

    //前面有格式化参数相关的操作,一般情况下定义类会把三个参数全传进来,分别为
    //类名、父类(可以为null、某个类或多个类组成的数组)和要声明类的属性及方法

    //定义一系列的变量供后面使用
    var proto, i, t, ctor, name, bases, chains, mixins = 1, parents = superclass;

    // 处理要声明类的父类
    if(opts.call(superclass) == "[object Array]"){
    //如果父类参数传过来的是数组,那么这里就是多继承,要用C3算法处理父类的关系
        //得到的bases为数组,第一个元素能标识真正父类(即superclass参数中的第一个)
        //在数组中的索引,其余的数组元素是按顺序排好的继承链,后面还会介绍到C3算法
    bases = c3mro(superclass, className);
    t = bases[0];
    mixins = bases.length - t;
    superclass = bases[mixins];
    }else{
    //此分支内是对没有父类或单个父类情况的处理,不再详述
    }
    //以下为构建类的原型属性和方法
    if(superclass){
    for(i = mixins - 1;; --i){
            //此处遍历所有需要mixin的类
            //注意此处,为什么说多个父类的情况下,只有第一个父类是真正的父类呢,因为在第一次循环的实例化了该父类,并记在了原型链中,而其它需要mixin的
            //父类在后面处理时会把superclass设为一个空的构造方法,合并父类原型链
            //后进行实例化
            proto = forceNew(superclass);
            if(!i){
                //此处在完成最后一个父类后跳出循环
                break;
             }
            // mix in properties
            t = bases[i];//得到要mixin的一个父类
            (t._meta ? mixOwn : mix)(proto, t.prototype);//合并原型链
            // chain in new constructor
            ctor = new Function;//声明一个新的Function
            ctor.superclass = superclass;
            ctor.prototype = proto;//设置原型链
            //此时将superclass指向了这个新的Function,再次进入这个循环的时候,实例//化的是ctor,而不是mixin的父类
            superclass = proto.constructor = ctor; 
    }
    }else{
    proto = {};
    }
    //此处将上面得到的方法(及属性)与要声明类本身所拥有的方法(及属性)进行合并
    safeMixin(proto, props);
    
    …………
    //此处收集链式调用相关的信息,后面会详述
    for(i = mixins - 1; i; --i){ // intentional assignment
    t = bases[i]._meta;
    if(t && t.chains){
        chains = mix(chains || {}, t.chains);
    }
    }
    if(proto["-chains-"]){
    chains = mix(chains || {}, proto["-chains-"]);
    }
    
    //此处根据上面收集的链式调用信息和父类信息构建最终的构造方法,后文详述
    t = !chains || !chains.hasOwnProperty(cname);
    bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :
    (bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t));

    //在这个构造方法中添加了许多的属性,在进行链式调用以及调用父类方法等处会用到
    ctor._meta  = {bases: bases, hidden: props, chains: chains,
    parents: parents, ctor: props.constructor};
    ctor.superclass = superclass && superclass.prototype;
    ctor.extend = extend;
    ctor.prototype = proto;
    proto.constructor = ctor;

    // 对于dojo.declare方法声明类的实例均有以下的工具方法
    proto.getInherited = getInherited;
    proto.inherited = inherited;
    proto.isInstanceOf = isInstanceOf;

    // 此处要进行全局注册
    if(className){
    proto.declaredClass = className;
    d.setObject(className, ctor);
    }

    //对于链式调用父类的那些方法进行处理,实际上进行了重写,后文详述
    if(chains){
    for(name in chains){
            if(proto[name] && typeof chains[name] == "string" && name != cname){
                t = proto[name] = chain(name, bases, chains[name] === "after");
                t.nom = name;
            }
    }
    }
    return ctor;	// Function
};

以上简单介绍了dojo声明类的整体流程,但是一些关键的细节如C3算法、链式调用在后面会继续进行介绍。

2.C3算法的实现

通过以前的文章和上面的分析,我们知道dojo的类声明支持多继承。在处理多继承时,不得不面对的就是继承链如何构造,比较现实的问题是如果多个父类都拥有同名的方法,那么在调用父类方法时,要按照什么规则确定调用哪个父类的呢?在解决这个问题上dojo实现了C3父类线性化的方法,对多个父类进行合理的排序,从而完美解决了这个问题。

相关厂商内容

C++11核心特性简析

1号店通用精准化平台架构以及大数据营销实践

使用Apache Mesos伸缩Docker容器

Azul Systems CTO Gil Tene谈Java性能优化

Java开发者:测试你的技能!参加IBM Code Rally 2015

 

相关赞助商

QCon全球软件开发大会,2015年10月15-17日,上海光大会展中心国际大酒店。8折报名。

 

为了了解继承链的相关知识,我们看一个简单的例子:

dojo.declare("A",null);
dojo.declare("B",null);
dojo.declare("C",null);
dojo.declare("D",[A, B]);
dojo.declare("E",[B, C]);
dojo.declare("F",[A, C]);
dojo.declare("G",[D, E]);

以上的代码中,声明了几个类,通过C3算法得到G的继承顺序应该是这样G->E->C->D->B->A的,只有按照这样的顺序才能保证类定义和依赖是正确的。那我们看一下这个C3算法是如何实现的呢:

function c3mro(bases, className){
    //定义一系列的变量
    var result = [], roots = [{cls: 0, refs: []}], nameMap = {}, clsCount = 1,
        l = bases.length, i = 0, j, lin, base, top, proto, rec, name, refs;

    //在这个循环中,构建出了父类各自的依赖关系(即父类可能会依赖其它的类)
    for(; i < l; ++i){
        base = bases[i];//得到父类
        …………
        //在dojo声明的类中都有一个_meta属性,记录父类信息,此处能够得到包含本身在//内的继承链
        lin = base._meta ? base._meta.bases : [base];
        top = 0;
        for(j = lin.length - 1; j >= 0; --j){
            //遍历继承链中的元素,注意,这里的处理是反向的,即从最底层的开始,一直到链的顶端
            proto = lin[j].prototype;
            if(!proto.hasOwnProperty("declaredClass")){
                proto.declaredClass = "uniqName_" + (counter++);
            }
            name = proto.declaredClass;
            // nameMap以map的方式记录了用到的类,不会重复
            if(!nameMap.hasOwnProperty(name)){
                //每个类都会有这样一个结构,其中refs特别重要,记录了引用了依赖类
                nameMap[name] = {count: 0, refs: [], cls: lin[j]};
                ++clsCount;
            }
            rec = nameMap[name];
            if(top && top !== rec){
                //满足条件时,意味着当前的类依赖此时top引用的类,即链的前一元素
                rec.refs.push(top);
                ++top.count;
            }
            top = rec;//top指向当前的类,开始下一循环
        }
        ++top.count;
        roots[0].refs.push(top);//在一个父类处理完成后就将它放在根的引用中
    }
    //到此为止,我们建立了父类元素的依赖关系,以下要正确处理这些关系
    while(roots.length){
        top = roots.pop();
        //将依赖的类插入到结果集中
        result.push(top.cls);
        --clsCount;
        // optimization: follow a single-linked chain
        while(refs = top.refs, refs.length == 1){
            //若当前类依赖的是一个父类,那处理这个依赖链
            top = refs[0];
            if(!top || --top.count){
                //特别注意此时有一个top.count变量,是用来记录这个类被引用的次数,
				//如果减一之后,值还大于零,就说明后面还有引用,此时不做处理,这也就是
				//在前面的例子中为什么不会出现G->E->C->B的原因
                top = 0;
                break;
            }
            result.push(top.cls);
            --clsCount;
        }
        if(top){
	        //若依赖多个分支,则将依赖的类分别放到roots中,这段代码只有在多继承,//第一次进入时才会执行
            for(i = 0, l = refs.length; i < l; ++i){
                top = refs[i];
                if(!--top.count){
                    roots.push(top);
                }
            }
        }
    }
    if(clsCount){
        //如果上面处理完成后,clsCount的值还大于1,那说明出错了
        err("can't build consistent linearization", className);
    }

    //构建完继承链后,要标识出真正父类在链的什么位置,就是通过返回数组的第一个元素
    base = bases[0];
    result[0] = base ?
    base._meta && base === result[result.length - base._meta.bases.length] ?
        base._meta.bases.length : 1 : 0;

    return result;
}

通过以上的分析,我们可以看到,这个算法实现起来相当复杂,如果朋友们对其感兴趣,建议按照上文的例子,自己加断点进行调试分析。dojo的作者使用了不到100行的代码实现了这样强大的功能,里面有很多值得借鉴的设计思想。

3. 链式构造器的实现

在第一部分代码分析中我们曾经看到过定义构造函数的代码,如下:

bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :
    (bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t));

这个方法对与理解dojo类机制很重要。从前一篇文章的介绍中,我们了解到默认情况下,如果dojo声明的类存在继承关系,那么就会自动调用父类的构造方法,且是按照继承链的顺序先调用父类的构造方法,但是从1.4版本开始,dojo提供了手动设置构造方法调用的选项。在以上的代码中涉及到dojo声明类的三个方法,如果该类没有父类,那么调用的就是singleConstructor,如果有父类的话,那么默认调用的是chainedConstructor,如果手动设置了构造方法调用,那么调用的就是simpleConstructor,要启动这个选项只需在声明该类的时候添加chains的constructor声明即可。

比方说,我们在定义继承自com.levinzhang.Person的com.levinzhang.Employee类时,可以这样做:

dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{
    "-chains-": {
        constructor:"manual"
    },
    …………
}

添加以上代码后,在构造com.levinzhang.Employee实例时,就不会再调用所有父类的构造方法了,但是此时我们可以使用inherited方法显式的调用父类方法。

限于篇幅,以上的三个方法不全部介绍,只介绍chainedConstructor的核心实现:

function chainedConstructor(bases, ctorSpecial){ 
    return function(){ 
        //在此之前有一些准备工作,不详述了 
        //找到所有的父类,分别调用其构造方法 
        for(i = l - 1; i >= 0; --i){ 
            f = bases[i]; 
            m = f._meta; 
            f = m ? m.ctor : f;
            //得到父类的构造方法 
            if(f){ 
                //通过apply调用父类的方法 
                f.apply(this, preArgs ? preArgs[i] : a); 
            } 
        } 
        // 请注意在构造方法执行完毕后,会执行名为postscript的方法,而这个方法是
        //dojo的dijit组件实现的关键生命周期方法 
        f = this.postscript; if(f){ 
            f.apply(this, args); 
        } 
    }; 
}

4. 调用父类方法的实现

在声明dojo类的时候,如果想调用父类的方法一般都是通过使用inherited方法来实现,但从1.4版本开始,dojo支持链式调用所有父类的方法,并引入了一些AOP的概念。我们将会分别介绍这两种方式。

通过inherited方式调用父类方法

在上一篇文章中,我们曾经介绍过,通过在类中使用inherited就可以调用到。这里我们要深入inherited的内部,看一下它的实现原理。因为inherited支持调用父类的一般方法和构造方法,两者略有不同,我们关注调用一般方法的过程。

function inherited(args, a, f){
    …………
    //在此之前有一些参数的处理
    if(name != cname){
        //不是构造方法
        if(cache.c !== caller){
            //在此之间的一些代码解决了确定调用者的问题,即确定从什么位置开始找父类
        }
        //按照顺序找父类的同名方法
        base = bases[++pos];
        if(base){
            proto = base.prototype;
            if(base._meta && proto.hasOwnProperty(name)){
                f = proto[name];//找到此方法了
            }else{
                //如果没有找到对应的方法将按照继承链依次往前找
                opf = op[name];
                do{
                    proto = base.prototype;
                    f = proto[name];
                    if(f && (base._meta ? proto.hasOwnProperty(name) : f !== opf)){
                        break;
                    }
                }while(base = bases[++pos]); // intentional assignment
            }
        }
        f = base && f || op[name];
    }else{
        //此处是处理调用父类的构造方法
    }
    if(f){
        //方法找到后,执行
        return a === true ? f : f.apply(this, a || args);
    }
}

链式调用父类方法

这是从dojo 1.4版本新加入的功能。如果在执行某个方法时,也想按照一定的顺序执行父类的方法,只需在定义类时,在-chains-属性中加以声明即可。

dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{
    "-chains-": {
     sayMyself:    "before"
	},
    ……
}

添加了以上声明后,意味着Employee及其所有的子类,在调用sayMyself方法时,都会先调用本身的同名方法,然后再按照继承链依次调用所有父类的同名方法,我们还可以将值“before”替换为“after”,其执行顺序就会反过来。在-chains-属性中声明的方法,在类定义时,会进行特殊处理,正如我们在第一章中看到的那样:

if(chains){
    for(name in chains){
        if(proto[name] && typeof chains[name] == "string" && name != cname){
        t = proto[name] = chain(name, bases, chains[name] === "after");
        t.nom = name;
        }
    }
}

我们可以看到,在-chains-中声明的方法都进行了替换,换成了chain方法的返回值,而这个方法也比较简单,源码如下:

function chain(name, bases, reversed){
    return function(){
        var b, m, f, i = 0, step = 1;
        if(reversed){
            //判定顺序,即"after"还是"before",分别对应于循环的不同起点和方向
            i = bases.length - 1;
            step = -1;
        }
        for(; b = bases[i]; i += step){ 
            //按照顺序依次查找父类
            m = b._meta;
            //找到父类中同名的方法
            f = (m ? m.hidden : b.prototype)[name];
            if(f){
                //依次执行
                f.apply(this, arguments);
            }
        }
    };
}

5.工具方法和属性如isInstanceOf、declaredClass的实现

除了上面提到的inherited方法以外,dojo在实现类功能的时候,还实现了一些工具方法和属性,这里介绍一个方法isInstanceOf和一个属性declaredClass。从功能上来说isInstanceOf方法用来判断一个对象是否为某个类的实例,而declaredClass属性得到的是某个对象所对应声明类的名字。

function isInstanceOf(cls){
    //得到实例对象继承链上的所有类
    var bases = this.constructor._meta.bases; 
    //遍历所有的类,看是否与传进来的类相等
    for(var i = 0, l = bases.length; i < l; ++i){
        if(bases[i] === cls){
            return true;
        }
    }
    return this instanceof cls;
}

而declaredClass属性的实现比较简单,只是在声明类的原型上添加了一个属性而已,类的实例对象就可以访问这个属性得到其声明类的名字了。这段代码在dojo.declare方法中:

if(className){
    proto.declaredClass = className;
    d.setObject(className, ctor);
}

在dojo实现类机制的过程中,有一些内部的方法,是很值得借鉴的,如forceNew、safeMixin等,这些方法在实现功能的同时,保证了代码的高效执行,感兴趣的朋友可以进一步研究。

6.总结与思考

  1. dojo在实现类机制方面支持多继承方式,其它JavaScript类库中很少能做到,而利用JavaScript原生语法实现多继承也较为困难。在这一点上dojo的类机制的功能确实足够强大。但是多继承会增加编码的难度,对开发人员如何组织类也有更高的要求;
  2. 链式调用父类方法时,我们可以看到dojo引入了许多AOP的理念,在1.7的版本中,将会有单独的模块提供AOP相关的支持,我们将会持续关注相关的功能;
  3. 在dojo的代码中,多处都会出现方法替换,如链式方法调用、事件绑定等,这种设计思想值得我们关注和学习;
  4. 使用了许多的内部属性,如_meta、bases等,这些元数据在实现复杂的类机制中起到了至关重要的作用,在进行源码分析的时候,我们可以给予关注,如果要实现类似功能也可以进行借鉴。

探究类库的实现原理是提高自己编码水平的好办法,类似于dojo这样类库的核心代码基本上每一行都有其设计思想在里面(当然也不可以盲目崇拜),每次阅读和探索都会有所发现和心得,当然里面肯定也会有自以为是或谬误之处,在此很乐意和读到这篇文章的朋友们一起研究,欢迎批评指正。

参考资料:

  • http://docs.dojocampus.org/
  • http://blog.csdn.net/dojotoolkit/
  • http://dojotoolkit.org/

关于作者

张卫滨,关注企业级Java开发和RIA技术,个人博客:http://lengyun3566.iteye.com,微博:http://weibo.com/zhangweibin1981


DOJO中的面向对象__第一章 JS中的对象模型

博客分类:
     
  • Dojo
Dojoprototype编程JavaScript

DOJO中的面向对象

  在JS中并没有Java等面向对象语言中常见的类(class)的概念。也就是说,JS中的对象并非基于类的。它仅仅为开发者提供了一些原类型和基本的内置对象。从写法上来看,它更加的类似于面向过程式的语言,函数仿佛成了JS中的顶级实体。事实上,JS是一门函数式编程的语言。所以当我们需要以面向对象的方式来构建大型web应用时,原生态的JS并不能很好的满足这一点。而DOJO的出现完美的解决了这个问题,它使得程序员能够以传统的面向对象的思维来进行开发,进而使JS用起来更加得心应手。

第一章 JS中的对象模型

(一) 构造器(伪类)

  在JS中创建一个对象很容易,JS提供了语法糖允许我们以字面量的方式来构建对象。例如:

Js代码  DOJO中的面向对象__第二章 Dojo中的类

(一) 利用DOJO.DECLARE声明类

  在第一章中讲到,JS提供的是一种基于原型(Prototype)的对象系统。这与JS诞生的初衷有关,在把JS仅仅用作页面脚本时,利用Prototype是一种优势。因为基于原型的对象不仅节约内存,初始化速度快,更重要的是还拥有了原型继承带来的动态特性。但是如果需要在RIA环境下构建丰富的web应用,JS的原型系统又显得不太给力。在继承性方面,原型系统能够让两个对象之间发生继承关系,但需要经过构造器来衔接。在封装性方面,JS的表现很糟糕,除了没有关键字支持,JS实质上也只有‘全局’和‘函数’两种级别的作用域。在多态性方面,由于缺少静态类型检查,JS对象无法准确识别子类或者父类的同名方法,更无法调用父类中已经定义的方法。所以归根结底的说,JS里采用的原型的对象系统并不是完备的面向对象系统。

  对此,Dojo在面向对象的一些特性方面对JS进行了补充,使得开发者能够尽可能的利用Dojo以一种基于类的面向对象思想来组织构建程序。Dojo中提供了一种简单的类型声明方式——dojo.declare,我们可以用来方便的对类型进行描述。如下所示,dojo.declare的格式很简单:

Js代码  DOJO中的面向对象__第三章 Dojo中的多继承 博客分类:
     
  • Dojo
DojoPythonCC++C# 

(一) 定义多继承

  Dojo在基于类的面向对象系统方面增强了JS的表现力,在第二章中已经提到Dojo还允许用户使用多继承,本章将主要探讨关于多继承的内容。利用dojo.declare声明多继承的类是很方便的,用户只需要传递一个数组(superclass )进去,superclass数组包含了所有的父类。

Js代码  DOJO中的面向对象__第三章补充:关于preamble、postscript以及chain 博客分类:
     
  • Dojo
DojoCC++C#Ajax

  这是dojo.declare中的三个极度蛋疼的功能,在对多继承的实质有所了解之后,才会加深对这三个功能的认识,所以放到最后说。这里就不谈它们的实现原理了,第四章中也许会描述到= =!

  如果觉得运行constructor前后缺少了些什么,那么preamble、postscript可以很好的帮助我们进行弥补。根据我时间不长的开发经验,还想不出什么情况下需要这种操作来弥补。如果在类型的定义中包含了preamble方法,那么在这个类型的构造函数被调用之前,会首先执行一次preamble。同样如果定义了postscript方法,那么该类型的构造函数被调用之后,也会自动执行一遍postscript。下面是一个简单的例子:

Js代码  DOJO中的面向对象__第四章 Dojo/_base/declare.js源码剖析(1) 博客分类:
     
  • Dojo
DojoprototypeCC++C#

  declare.js中包含了整个dojo面向对象中最重要的代码,即对类型表达和扩展的一些封装。功能虽然强大,但是幸好文件并不复杂,拥有清晰的脉络。整个declare.js一共定义了15个函数,14个具名函数,1个匿名函数。这14个具名函数中又有一些是declare.js内部使用的函数,外部无法调用,还有一些是由dojo提供的接口,可以供dojo.declare声明的类型来调用。具体函数如下所示:

Js代码  1. http://www.iteye.com/topic/461096

2. http://ejohn.org/blog/objectgetprototypeof

(四)  chainedConstructor方法

  第四章中提过,在declare中构建ctor的时候,针对不同的情况分别调用了三个函数。这里只研究其中的chainedConstructor,因为该函数最为复杂,另外两个simpleConstructor和singleConstructor函数不作详细分析。chainedConstructor是当declare的类型存在继承,且未设置constructor="manual"时调用的函数,返回值是一个用以充当declare类型的函数。

  整个chainedConstructor可以看作三大步。第一步 就是就是执行在继承结构链上所有类型中定义的preamble函数,第二步 是调用所有类型的constructor函数,第三步 是执行当前类型中定义的postscript方法。如果撇开preamble和postscript,那么整个chainedConstructor的实现就只需要调用所有类型的constructor函数,相应的实现也可以被压缩成:

Js代码  收藏代码
  1. // 遍历bases中的类型,未指明constructor的调用顺序,因此默认是after,即  
  2. // 从bases[i]---->bases[0]依次调用constructor  
  3. for(i = bases.length - 1; i >= 0; --i){  
  4.     f = bases[i];  
  5.     m = f._meta;  
  6.     f = m ? m.ctor : f;//可能是raw class  
  7.     if(f){  
  8.         f.apply(this, arguments);  
  9.     }  
  10. }  

  可惜必须要面对存在preamble和postscript的情况,而且这两种情况的处理方式还不一样。其实preamble的调用也很简单,就是遍历bases,找出其中定义过的preamble函数,然后依次执行。

Js代码  收藏代码
  1. function chainedConstructor(bases, ctorSpecial){  
  2.     return function(){  
  3.         var a = arguments, args = a, a0 = a[0], f, i, m,l = bases.length, preArgs;  
  4.   
  5.         //如果不是利用new的时候调用该函数,那要强迫new出实例  
  6.         if(!(this instanceof a.callee)){  
  7.             // not called via new, so force it  
  8.             return applyNew(a);  
  9.         }  
  10.   
  11.         // ctorSpecial=true仅发生在:没有定义chains,或者chains中的constructor  
  12.         // 一旦有了chains并且设置了constructor的顺序,则无需执行preamble  
  13.         // 有两种设置preamble的方式:  
  14.         // 一是在new的时候将preamble作为参数传递给该类型  
  15.         // 二是在declare一个类型的时候定义好  
  16.         if(ctorSpecial && (a0 && a0.preamble || this.preamble)){  
  17.             preArgs = new Array(bases.length);  
  18.             preArgs[0] = a;  
  19.             for(i = 0;;){  
  20.                 // 如果是在参数中定义了preamble并传第给ctor  
  21.                 a0 = a[0];  
  22.                 if(a0){  
  23.                     f = a0.preamble;  
  24.                     if(f){  
  25.                         a = f.apply(this, a) || a;  
  26.                     }  
  27.                 }  
  28.                 // 如果是在类型的declare中定义preamble  
  29.                 f = bases[i].prototype;  
  30.                 f = f.hasOwnProperty("preamble") && f.preamble;  
  31.                 if(f){  
  32.                     a = f.apply(this, a) || a;  
  33.                 }  
  34.                 // for循环再遍历完了bases之后会结束  
  35.                 if(++i == l){  
  36.                     break;  
  37.                 }  
  38.                 //会记录下每次调用preamble之后的arguments  
  39.                 //主要是为了防止某个preamble对arguments作出修改  
  40.                 preArgs[i] = a;  
  41.             }  
  42.         }  
  43.           
  44.         //第二步开始  
  45.         ……  
  46.   
  47. }  

  上面对第一步preamble的执行作出了一些解释。现在还剩最后一步,只针对当前的类型来调用postscript:

Js代码  收藏代码
  1. f = this.postscript;  
  2. if(f){  
  3.      f.apply(this, args);  
  4. }  

  这个实在没什么好讲的。

  从整个chainedConstructor的三大步骤实现来看,其实dojo的源码写的还是很通俗易懂的,结构也很清楚,是不错的学习材料^_^至此declare.js中比较重要的函数基本都已经讲完了,只缺少一个关于c3mro函数的剖析,但是前面讲mro已经花了大量的篇幅,便不打算再写下去了。以前都是仅仅停留在参阅Dojo的API的说明上,这是我第一次花力气去阅读Dojo的源码,可惜目前的工作中已经没有机会再使用Dojo了。