zl程序教程

您现在的位置是:首页 >  其他

当前栏目

解析JohnResigSimpleJavaScriptInheritance代码

代码 解析
2023-06-13 09:14:42 时间

由于作者翻译会加入自己的理解以便自己学习和使用,如果英文好的同学可看下面  如文章中有翻译错误还请留言.交流并改正.(:
======================Enein翻译=========================

       JohnResig写了一篇关于JavaScript里类似其它语言的"继承",灵感来自于 base2andPrototypeJS. 他为文章起名为"SimpleJavaScriptInheritance".他使用的一些很巧妙的技术来实现super方法.
       你还可以看原文也会有详细的说明,他也在他的"SecretsofaJavaScriptNinja"里有所介绍.在书中可能方法有一些不同,它在Object中加入了subClass方法,而不是创建一个全局变量.
OriginalScript-JohnResigSimpleJavaScriptInheritance
下面是原谅代码,我移除了一些注释使用它看起来更清晰.

复制代码代码如下:

(function(){
varinitializing=false,fnTest=/xyz/.test(function(){xyz;})?/\b_super\b/:/.*/;
this.Class=function(){};
Class.extend=function(prop){
var_super=this.prototype;
initializing=true;
varprototype=newthis();
initializing=false;
for(varnameinprop){
prototype[name]=typeofprop[name]=="function"&&
typeof_super[name]=="function"&&fnTest.test(prop[name])?
(function(name,fn){
returnfunction(){
vartmp=this._super;
this._super=_super[name];
varret=fn.apply(this,arguments);
this._super=tmp;
returnret;
};
})(name,prop[name]):
prop[name];
}
functionClass(){
if(!initializing&&this.init)
this.init.apply(this,arguments);
}
Class.prototype=prototype;
Class.constructor=Class;
Class.extend=arguments.callee;
returnClass;
};
})();

BreakdownoftheSimpleInheritancescript
下面我们来分析一下,它是如何实现和有哪些技术被使用.

复制代码代码如下:


(function(){//...})();

首先我们创建一个自执行匿名函数,为代码创建一个作用域.

复制代码代码如下:     
varinitializing=false

这initializing变量意思很直接,它是boolean来检查ClassFunction(稍后介绍)什么时候被调用.在创建实例时设置initializing为true/false或者只是返回一个对象指向当前的原型链上来达到"继承"的目的.

如果我们创建一个实例(initializing==false),正好Class有一个init方法,这样init会自动执行。再或者,如果我们仅仅将它分配给原型上(initializing==true),将不会发生什么,init方法不会被执行。这样做是为了避免每次调用构造方法都要执行init方法.(varprototype=newthis());.

复制代码代码如下:
fnTest=/xyz/.test(function(){xyz;})?/\b_super\b/:/.*/;

         这个fnTest的目的就是为了验证classmethod中是否使用了"_super()"调用.这种技术叫做"functiondecompilation(函数反编译)"也叫做"functionserialisation(函数序列化)",Functionserialisation是在一个函数被转换成字符串时发生的.现在很多浏览器都支持toString方法。

测试Functionserialisation,fnTest使用一个匿名函数funciton(){xyz;}设置内容为"xyz",在转变成字符串后使用正则对"xyz"进行查找.它将返回true(如果浏览器支持functionserialisation)因为函数将转变成字符串所以"xyz"也民属于字符串的一部分.在这个例子中fnTest将返回"/\b_super\b/",另一种则返回"/.*/"如果浏览器不支持functionserialisation则始终返回true。(这个指的是原始代码中的fnTest.test)使用fnTest正则,和函数序列化技术,我们能很容易方法中是否使用了"_super"如果它们使用,则执行一些特殊方法.反之正常. 这个特殊方法是为了避免在父类与子类中同时出现同一个方法.父类将会被覆盖. 

       浏览器不支持Functionserialisation将会始终返回true,那么会始终对_super进行额外的操作,导致这些新的方法不能在_super中使用.这会有一些小的性能消耗.但能保证在所有浏览器中正常执行.

复制代码代码如下:
this.Class=function(){};

       创建一个空的构造方法,放到全局变量中.这将会是最上层的构造方法.它没有定义内容,或一个原型对象.除了下面的extends方法.this指的是window对象.使Class变量为全局对象.

复制代码代码如下:     
Class.extend=function(prop){//...}

       加入extends方法和一个简单的prop(一个对象)参数.它将返回新构造方法的原型+父对象的原型; 

复制代码代码如下:
var_super=this.prototype;

       将当前对象的原型对象存储在_super中.this.prototype是被扩展对象的原型,它可以访问父级方法在你需要的地方, 这个变量叫什么_super,是因为super是保留字.尽管现在还没有应用起来.

复制代码代码如下:
initializing=true;varprototype=newthis();initializing=false;

       实例class对象存储在prototype变量中,但不执行init方法.之前设置initializing为true所以在newClass的时候不会fireinit方法.prototype变量分配后,initializing被设置回false,为了下一步可以正常工作.(e.g当想要创建一个真正的实例的时候)

复制代码代码如下:   
for(varnameinprop){//...}

       使用一个for循环,我们迭代出prop里的属性和方法.该属性是通过extends方法传递进来的,除了一些对_super的特殊处理,我们将值赋给prototype属性.

复制代码代码如下:   
prototype[name]=typeofprop[name]=="function"&&typeof_super[name]=="function"&&fnTest.test(prop[name])?(function(name,fn){returnfunction(){ //specialhandlingfor_super};})(name,prop[name]):prop[name];

       当我们遍历prop里的每个对象时,如果满足(typeofprop[name]=="function") (typeof_super[name]=="function")(fnTest.test(prop[name])==true)
我们将会加入新的方法来处理绑定到父类新的方法以及原始方法.
       以上方式代码看起来可能很有些混乱下面改使用一种清晰的方式查看一下.

复制代码代码如下:    
if(typeofprop[name]=="function"&&typeof_super[name]=="function"&&fnTest.test(prop[name])){prototype[name]=(function(name,fn){returnfunction(){ //specialhandlingfor_super};})(name,prop[name]);}else{//justcopythepropertyprototype[name]=prop[name];}

       另一个自执行匿名函数,在处理super中的nameprop[name]被使用.没有这个闭包.当返回这个function时这个变量的引用将会出错.(e.g它始终会返回循环的最后一个)
       遍历所有,我们将返回一个新的函数,这个函数来处理原生方法(viasuper)和新方法.

复制代码代码如下:       
//specialhandlingforsupervartmp=this._super;this._super=_super[name];varret=fn.apply(this,arguments);this._super=tmp;returnret;

       对super的特殊处理,我们首先要存储已存在_super属性和类的一些参数.存储在临时tmp里,这是为了防止_super中已存在的方法被重写
完事儿后我们将tmp在赋给this._super这样它就可以正常工作了.
        下一步,我们将_super[name]方法赋给当前对象的this._super,这样当fn通过apply被执行的时候this._super()就会指向父类方法,这个
父类方法中的this也同样可以访问当前对象.
        最后我们将返回值存储在ret中,在将_super设置回来后返回该对象.
       下面有个简单的例子, 定义个简单的Foo,创建继承对象Bar:

复制代码代码如下:
varFoo=Class.extend({qux:function(){return"Foo.qux";}});varBar=Foo.extend({qux:function(){return"Bar.qux,"+this._super();}});

        当Foo.extends被执行,在qux方法中由于存在this._super所以Bar原型上的qux实际上应该是这样的:

复制代码代码如下:      
Bar.prototype.qux=function(){vartmp=this._super;this._super=Foo.prototype.qux;varret=(function(){return"Bar.qux,"+this._super();}).apply(this,arguments);this._super=tmp;returnret;}

       在脚本中完成这步后,构造方法将被调用

复制代码代码如下:
functionClass(){if(!initializing&&this.init)this.init.apply(this,arguments);}

       这段代码调用Class创建一个新的构造方法,这不同于之前创建的this.Class,作为本地的Class.extend.这个构造方法返回Class.extend的调用(比如之前Foo.extends). newFoo()实例后这个构造方法将被执行.
       构造方法将会自动执行init()方法(如果存在的话)正好上面说的那样,这个initializing变量来控制init是否被执行.

复制代码代码如下:      
Class.prototype=prototype;

       最后这个prototype, 从父类的构造方法返回一个混合后的父类原型对象.(e.gvarprototype=newthis()),这个结果是通过extend函数里的for循环.

Class.constructor=Class;
       因为我们重写了整个原型对象,在这个类型中存储这个原生的构造方法, 让它在一个实例的构造方法中能保持默认形为.

复制代码代码如下:       
Class.extend=arguments.callee;

       将赋其自身,通过 arguments.callee,在本例中表示“自身”其实这里我们可以避免使用arguments.callee,如果我们修改一下我的原生方法(e.gClass.extend=functionextend(prop))之后我们就可以通过使用

复制代码代码如下:       
Class.extend=extend;.returnClass;

       实例之后会返回,一个原型对象,一个构造属性,一个extend方法和一个可自执行的方法init.!!!