【cocos2d-x从c++到js】05:John Resiq的继承写法解析

今天,我们来看看John Resiq的继承写法Simple JavaScript Inheritance。之前已经有很多同行分析过了。这个写法在cocos2d-x for js中也被使用,并作了少许改动。我尝试着做一些展开描述。

先贴源码:


  1. cc.Class = function(){};  
  2. cc.Class.extend = function (prop) { 
  3.     var _super = this.prototype;  
  4.  
  5.     // Instantiate a base class (but only create the instance, 
  6.     // don't run the init constructor) 
  7.     initializing = true
  8.     var prototype = new this(); 
  9.     initializing = false
  10.     fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 
  11.  
  12.     // Copy the properties over onto the new prototype 
  13.     for (var name in prop) { 
  14.         // Check if we're overwriting an existing function 
  15.         prototype[name] = typeof prop[name] == "function" && 
  16.             typeof _super[name] == "function" && fnTest.test(prop[name]) ? 
  17.             (function (name, fn) { 
  18.                 return function () { 
  19.                     var tmp = this._super; 
  20.  
  21.                     // Add a new ._super() method that is the same method 
  22.                     // but on the super-class 
  23.                     this._super = _super[name]; 
  24.  
  25.                     // The method only need to be bound temporarily, so we 
  26.                     // remove it when we're done executing 
  27.                     var ret = fn.apply(this, arguments); 
  28.                     this._super = tmp; 
  29.  
  30.                     return ret; 
  31.                 }; 
  32.             })(name, prop[name]) : 
  33.             prop[name]; 
  34.     } 
  35.  
  36.     // The dummy class constructor 
  37.     function Class() { 
  38.         // All construction is actually done in the init method 
  39.         if (!initializing && this.ctor) 
  40.             this.ctor.apply(this, arguments); 
  41.     } 
  42.  
  43.     // Populate our constructed prototype object 
  44.     Class.prototype = prototype; 
  45.  
  46.     // Enforce the constructor to be what we expect 
  47.     Class.prototype.constructor = Class; 
  48.  
  49.     // And make this class extendable 
  50.     Class.extend = arguments.callee; 
  51.  
  52.     return Class; 
  53. }; 

 


  1. cc.Class = function(){};  

做了一个全局构造函数Class,这个不需要什么解释。


  1. cc.Class.extend = function (prop) {  

prop是一个对象字面量,这个对象包含了子类所需要的全部成员变量和成员方法。extend函数就在内部遍历这个字面量的属性,然后将这些属性绑定到一个“新的构造函数”(也就是子类的构造函数)的原型上。


  1. var _super = this.prototype; 

注意,js里面的这个this的类型是在调用时指定的,那么这个this实际上是父类构造函数对象。比如你写了一个MyNode继承自cc.Node。相应代码是:


  1. var MyNode = cc.Node.extend({ 
  2. var _super = this.prototype;
  3. ... 
  4. }); 

那么这个this就是父类cc.Node。


  1. initializing = true
  2. var prototype = new this(); 
  3. initializing = false

生成父类的对象,用于给子类绑定原型链。但要注意,因为这个时候,什么实参都没有,并不应该给父类对象中的属性进行初始化(构造器参数神马的木有怎么初始化啊喵,这玩意实际是JS语言设计上的失误造成的)。所以用一个变量做标记,防止在这个时候进行初始化。相关代码在后面就会看到。


  1. fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;  

这玩意看起来很乱,这是一个正则对象,右边是一个?表达式,中间添加了一些正则代码。这个对象的作用是,检测子类函数中是否有调用父类的同名方法“_super()”(这种调用父类方式是由John Resiq约定的)。但这种检测需要JS解释器支持把一个函数转换为字符串的功能,有些解释器是不支持的。所以我们先做一个检测,自己造了一个函数,里面有xyz,然后用正则的test函数在里面搜索xyz。如果返回true,表示支持函数转字符串,那么就直接返回/\b_super\b/否则返回/.*/


  1.  for (var name in prop) {  
  2.         prototype[name] = typeof prop[name] == "function" &&  
  3.             typeof _super[name] == "function" && fnTest.test(prop[name]) ?  
  4.             (function (name, fn) {  
  5.                 return function () {  
  6.                     var tmp = this._super;  
  7.                     this._super = _super[name];   
  8.                     var ret = fn.apply(this, arguments);   
  9. this._super = tmp;
  10.                     return ret;  
  11.                 };  
  12.             })(name, prop[name]) :  
  13.             prop[name];  
  14. }  

现在重头戏来了,在这个地方我们要把传进来的那个字面量prop的属性全都绑定到原型上。这地方又他喵的是一个?表达式,JR实在太喜欢用这玩意了。首先,forin把属性拿出来。然后,因为我们添加的功能是“实现像c++那样通过子类来调用父类的同名函数”,那么需要检测父类和子类中是否都有这两个同名函数。用的是这段代码:


  1. typeof prop[name] == "function" && typeof _super[name] == "function" 

然后,我们还要检测,子类函数中是否真的使用了_super去调用了同名的父类函数。这个时候,之前的正则对象fnTest出场。继续之前的话题,如果解释器支持函数转字符串,那么fnTest.test(prop[name])可以正常检测,逻辑正常进行;如果不支持,那么fnTest.test(prop[name])始终返回true。

这玩意什么用处,这是一个优化,如果子类函数真的调父类函数了,就做一个特殊的绑定操作(这个操作我们后面马上讲),如果子类函数没有调父类函数,那么就正常绑定。如果没法判断是否调用了(解释器不支持函数转字符串),直接按照调用了那种情况来处理。虽然损失一些性能,但是可以保证不出问题。


  1. (function (name, fn) {   
  2.     return function () {   
  3.         var tmp = this._super;   
  4.         this._super = _super[name];    
  5.         var ret = fn.apply(this, arguments);    
  6.         this._super = tmp; 
  7.         return ret;   
  8.     };   
  9. })(name, prop[name]) 

继续,上面的就是我们说的那个特殊的绑定操作。在这里,我们做了一个闭包,这里面的this是子类对象,跟之前的那个this不一样哦。我们利用闭包的特性,保存了一个_super,这个_super被绑定了父类的同名函数_super[name]。然后我们使用fn.apply(this, arguments)调用子类函数,并保存返回值。因为这是一个闭包,所以根据语法,我们可以在fn的实现中调用_super函数。


  1. function Class() {  
  2.    if (!initializing && this.ctor)  
  3.         this.ctor.apply(this, arguments);  
  4. }  
  5.  
  6. Class.prototype = prototype;   
  7. Class.prototype.constructor = Class;   
  8. Class.extend = arguments.callee;  

生成一个Class构造函数,这个构造函数作为这个大匿名函数的返回值使用。然后里面就是之前说的,初始化保护,防止在绑定原型链的时候初始化。注意后面那个玩意ctor,在cocos2d-x for js中,真正的初始化是二段构造的那个init,而不是ctor。在cocos2d-x for js的实现中ctor里面会调用一个函数cc.associateWithNative(this, 父类),这个函数负责后台生成一个c++对象,然后把c++对象和js对象绑定到一起。

剩下的是例行公事:绑定子类的原型,修正子类的构造器指向它自己,给子类添加一个同样的extend方法。

最后把这个完成的构造函数返回出来。




 本文转自 老G 51CTO博客,原文链接:http://blog.51cto.com/goldlion/1127020,如需转载请自行联系原作者


上一篇:检查Prefab或场景物件是否丢失脚本


下一篇:记住看小电影前一定要检查一下域名是不是 HTTPS 的,不然……