您好,登錄后才能下訂單哦!
今天,我們來看看John Resiq的繼承寫法Simple JavaScript Inheritance。之前已經有很多同行分析過了。這個寫法在cocos2d-x for js中也被使用,并作了少許改動。我嘗試著做一些展開描述。
先貼源碼:
- cc.Class = function(){};
- cc.Class.extend = function (prop) {
- var _super = this.prototype;
- // Instantiate a base class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- var prototype = new this();
- initializing = false;
- fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
- // Copy the properties over onto the new prototype
- for (var name in prop) {
- // Check if we're overwriting an existing function
- prototype[name] = typeof prop[name] == "function" &&
- typeof _super[name] == "function" && fnTest.test(prop[name]) ?
- (function (name, fn) {
- return function () {
- var tmp = this._super;
- // Add a new ._super() method that is the same method
- // but on the super-class
- this._super = _super[name];
- // The method only need to be bound temporarily, so we
- // remove it when we're done executing
- var ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, prop[name]) :
- prop[name];
- }
- // The dummy class constructor
- function Class() {
- // All construction is actually done in the init method
- if (!initializing && this.ctor)
- this.ctor.apply(this, arguments);
- }
- // Populate our constructed prototype object
- Class.prototype = prototype;
- // Enforce the constructor to be what we expect
- Class.prototype.constructor = Class;
- // And make this class extendable
- Class.extend = arguments.callee;
- return Class;
- };
- cc.Class = function(){};
做了一個全局構造函數Class,這個不需要什么解釋。
- cc.Class.extend = function (prop) {
prop是一個對象字面量,這個對象包含了子類所需要的全部成員變量和成員方法。extend函數就在內部遍歷這個字面量的屬性,然后將這些屬性綁定到一個“新的構造函數”(也就是子類的構造函數)的原型上。
- var _super = this.prototype;
注意,js里面的這個this的類型是在調用時指定的,那么這個this實際上是父類構造函數對象。比如你寫了一個MyNode繼承自cc.Node。相應代碼是:
- var MyNode = cc.Node.extend({
- var _super = this.prototype;
- ...
- });
那么這個this就是父類cc.Node。
- initializing = true;
- var prototype = new this();
- initializing = false;
生成父類的對象,用于給子類綁定原型鏈。但要注意,因為這個時候,什么實參都沒有,并不應該給父類對象中的屬性進行初始化(構造器參數神馬的木有怎么初始化啊喵,這玩意實際是JS語言設計上的失誤造成的)。所以用一個變量做標記,防止在這個時候進行初始化。相關代碼在后面就會看到。
- fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
這玩意看起來很亂,這是一個正則對象,右邊是一個?表達式,中間添加了一些正則代碼。這個對象的作用是,檢測子類函數中是否有調用父類的同名方法“_super()”(這種調用父類方式是由John Resiq約定的)。但這種檢測需要JS解釋器支持把一個函數轉換為字符串的功能,有些解釋器是不支持的。所以我們先做一個檢測,自己造了一個函數,里面有xyz,然后用正則的test函數在里面搜索xyz。如果返回true,表示支持函數轉字符串,那么就直接返回/\b_super\b/否則返回/.*/
- for (var name in prop) {
- prototype[name] = typeof prop[name] == "function" &&
- typeof _super[name] == "function" && fnTest.test(prop[name]) ?
- (function (name, fn) {
- return function () {
- var tmp = this._super;
- this._super = _super[name];
- var ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, prop[name]) :
- prop[name];
- }
現在重頭戲來了,在這個地方我們要把傳進來的那個字面量prop的屬性全都綁定到原型上。這地方又他喵的是一個?表達式,JR實在太喜歡用這玩意了。首先,forin把屬性拿出來。然后,因為我們添加的功能是“實現像c++那樣通過子類來調用父類的同名函數”,那么需要檢測父類和子類中是否都有這兩個同名函數。用的是這段代碼:
- typeof prop[name] == "function" && typeof _super[name] == "function"
然后,我們還要檢測,子類函數中是否真的使用了_super去調用了同名的父類函數。這個時候,之前的正則對象fnTest出場。繼續之前的話題,如果解釋器支持函數轉字符串,那么fnTest.test(prop[name])可以正常檢測,邏輯正常進行;如果不支持,那么fnTest.test(prop[name])始終返回true。
這玩意什么用處,這是一個優化,如果子類函數真的調父類函數了,就做一個特殊的綁定操作(這個操作我們后面馬上講),如果子類函數沒有調父類函數,那么就正常綁定。如果沒法判斷是否調用了(解釋器不支持函數轉字符串),直接按照調用了那種情況來處理。雖然損失一些性能,但是可以保證不出問題。
- (function (name, fn) {
- return function () {
- var tmp = this._super;
- this._super = _super[name];
- var ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, prop[name])
繼續,上面的就是我們說的那個特殊的綁定操作。在這里,我們做了一個閉包,這里面的this是子類對象,跟之前的那個this不一樣哦。我們利用閉包的特性,保存了一個_super,這個_super被綁定了父類的同名函數_super[name]。然后我們使用fn.apply(this, arguments)調用子類函數,并保存返回值。因為這是一個閉包,所以根據語法,我們可以在fn的實現中調用_super函數。
- function Class() {
- if (!initializing && this.ctor)
- this.ctor.apply(this, arguments);
- }
- Class.prototype = prototype;
- Class.prototype.constructor = Class;
- Class.extend = arguments.callee;
生成一個Class構造函數,這個構造函數作為這個大匿名函數的返回值使用。然后里面就是之前說的,初始化保護,防止在綁定原型鏈的時候初始化。注意后面那個玩意ctor,在cocos2d-x for js中,真正的初始化是二段構造的那個init,而不是ctor。在cocos2d-x for js的實現中ctor里面會調用一個函數cc.associateWithNative(this, 父類),這個函數負責后臺生成一個c++對象,然后把c++對象和js對象綁定到一起。
剩下的是例行公事:綁定子類的原型,修正子類的構造器指向它自己,給子類添加一個同樣的extend方法。
最后把這個完成的構造函數返回出來。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。