JavaScript繼承詳解(五)

文章截圖 - 更好的排版javascript

 

在本章中,咱們將分析John Resig關於JavaScript繼承的一個實現 - Simple JavaScript Inheritance。 
John Resig做爲jQuery的創始人而聲名在外。是《Pro JavaScript Techniques》的做者,並且Resig將會在今年秋天推出一本書《JavaScript Secrets》,很是期待。java

調用方式

調用方式很是優雅: 
注意:代碼中的Class、extend、_super都是自定義的對象,咱們會在後面的代碼分析中詳解。jquery

var Person = Class.extend({
            // init是構造函數
            init: function(name) {
                this.name = name;
            },
            getName: function() {
                return this.name;
            }
        });
        // Employee類從Person類繼承
        var Employee = Person.extend({
            // init是構造函數
            init: function(name, employeeID) {
                //  在構造函數中調用父類的構造函數
                this._super(name);
                this.employeeID = employeeID;
            },
            getEmployeeID: function() {
                return this.employeeID;
            },
            getName: function() {
                //  調用父類的方法
                return "Employee name: " + this._super();
            }
        });

        var zhang = new Employee("ZhangSan", "1234");
        console.log(zhang.getName());   // "Employee name: ZhangSan"
說實話,對於完成本系列文章的目標-繼承-而言,真找不到什麼缺點。方法一如jQuery同樣簡潔明瞭。

 

代碼分析

爲了一個漂亮的調用方式,內部實現的確複雜了不少,不過這些也是值得的 - 一我的的思考帶給了無數程序員快樂的微笑 - 嘿嘿,有點肉麻。 
不過其中的一段代碼的確迷惑我一段時間:程序員

fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
我曾在幾天前的博客中寫過一篇文章專門闡述這個問題,有興趣能夠向前翻一翻。
// 自執行的匿名函數建立一個上下文,避免引入全局變量
        (function() {
            // initializing變量用來標示當前是否處於類的建立階段,
            // - 在類的建立階段是不能調用原型方法init的
            // - 咱們曾在本系列的第三篇文章中詳細闡述了這個問題
            // fnTest是一個正則表達式,可能的取值爲(/\b_super\b/ 或 /.*/)
            // - 對 /xyz/.test(function() { xyz; }) 的測試是爲了檢測瀏覽器是否支持test參數爲函數的狀況
            // - 不過我對IE7.0,Chrome2.0,FF3.5進行了測試,此測試都返回true。
            // - 因此我想這樣對fnTest賦值大部分狀況下也是對的:fnTest = /\b_super\b/;
            var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
            // 基類構造函數
            // 這裏的this是window,因此這整段代碼就向外界開闢了一扇窗戶 - window.Class
            this.Class = function() { };
            // 繼承方法定義
            Class.extend = function(prop) {
                // 這個地方非常迷惑人,還記得我在本系列的第二篇文章中提到的麼
                // - this具體指向什麼不是定義時能決定的,而是要看此函數是怎麼被調用的
                // - 咱們已經知道extend確定是做爲方法調用的,而不是做爲構造函數
                // - 因此這裏this指向的不是Object,而是Function(便是Class),那麼this.prototype就是父類的原型對象
                // - 注意:_super指向父類的原型對象,咱們會在後面的代碼中屢次遇見這個變量
                var _super = this.prototype;
                // 經過將子類的原型指向父類的一個實例對象來完成繼承
                // - 注意:this是基類構造函數(便是Class)
                initializing = true;
                var prototype = new this();
                initializing = false;
                // 我以爲這段代碼是通過做者優化過的,因此讀起來很是生硬,我會在後面詳解
                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];
                }
                // 這個地方能夠看出,Resig很會假裝哦
                // - 使用一個同名的局部變量來覆蓋全局變量,非常迷惑人
                // - 若是你以爲拗口的話,徹底可使用另一個名字,好比function F()來代替function Class()
                // - 注意:這裏的Class不是在最外層定義的那個基類構造函數
                function Class() {
                    // 在類的實例化時,調用原型方法init
                    if (!initializing && this.init)
                        this.init.apply(this, arguments);
                }
                // 子類的prototype指向父類的實例(完成繼承的關鍵)
                Class.prototype = prototype;
                // 修正constructor指向錯誤
                Class.constructor = Class;
                // 子類自動獲取extend方法,arguments.callee指向當前正在執行的函數
                Class.extend = arguments.callee;
                return Class;
            };
        })();
下面我會對其中的for-in循環進行解讀,把自執行的匿名方法用一個局部函數來替換, 這樣有利於咱們看清真相:
(function() {
            var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
            this.Class = function() { };
            Class.extend = function(prop) {
                var _super = this.prototype;
                initializing = true;
                var prototype = new this();
                initializing = false;

                // 若是父類和子類有同名方法,而且子類中此方法(name)經過_super調用了父類方法
                // - 則從新定義此方法
                function fn(name, fn) {
                    return function() {
                        // 將實例方法_super保護起來。
                        // 我的以爲這個地方沒有必要,由於每次調用這樣的函數時都會對this._super從新定義。
                        var tmp = this._super;
                        // 在執行子類的實例方法name時,添加另一個實例方法_super,此方法指向父類的同名方法
                        this._super = _super[name];
                        // 執行子類的方法name,注意在方法體內this._super能夠調用父類的同名方法
                        var ret = fn.apply(this, arguments);
                        this._super = tmp;
                        
                        // 返回執行結果
                        return ret;
                    };
                }
                // 拷貝prop中的全部屬性到子類原型中
                for (var name in prop) {
                    // 若是prop和父類中存在同名的函數,而且此函數中使用了_super方法,則對此方法進行特殊處理 - fn
                    // 不然將此方法prop[name]直接賦值給子類的原型
                    if (typeof prop[name] === "function" &&
                            typeof _super[name] === "function" && fnTest.test(prop[name])) {
                        prototype[name] = fn(name, prop[name]);
                    } else {
                        prototype[name] = prop[name];
                    }
                }

                function Class() {
                    if (!initializing && this.init) {
                        this.init.apply(this, arguments);
                    }
                }
                Class.prototype = prototype;
                Class.constructor = Class;
                Class.extend = arguments.callee;
                return Class;
            };
        })();

 

寫到這裏,你們是否以爲Resig的實現和咱們在第三章一步一步實現的jClass很相似。 其實在寫這一系列的文章以前,我已經對prototype、mootools、extjs、 jQuery-Simple-Inheritance、Crockford-Classical-Inheritance這些實現有必定的瞭解,而且大部分都在實際項目中使用過。 在第三章中實現jClass也參考了Resig的實現,在此向Resig表示感謝。 
下來咱們就把jClass改形成和這裏的Class具備相同的行爲。正則表達式

咱們的實現

將咱們在第三章實現的jClass改形成目前John Resig所寫的形式至關簡單,只須要修改其中的兩三行就好了:瀏覽器

(function() {
            // 當前是否處於建立類的階段
            var initializing = false;
            jClass = function() { };
            jClass.extend = function(prop) {
                // 若是調用當前函數的對象(這裏是函數)不是Class,則是父類
                var baseClass = null;
                if (this !== jClass) {
                    baseClass = this;
                }
                // 本次調用所建立的類(構造函數)
                function F() {
                    // 若是當前處於實例化類的階段,則調用init原型函數
                    if (!initializing) {
                        // 若是父類存在,則實例對象的baseprototype指向父類的原型
                        // 這就提供了在實例對象中調用父類方法的途徑
                        if (baseClass) {
                            this._superprototype = baseClass.prototype;
                        }
                        this.init.apply(this, arguments);
                    }
                }
                // 若是此類須要從其它類擴展
                if (baseClass) {
                    initializing = true;
                    F.prototype = new baseClass();
                    F.prototype.constructor = F;
                    initializing = false;
                }
                // 新建立的類自動附加extend函數
                F.extend = arguments.callee;

                // 覆蓋父類的同名函數
                for (var name in prop) {
                    if (prop.hasOwnProperty(name)) {
                        // 若是此類繼承自父類baseClass而且父類原型中存在同名函數name
                        if (baseClass &&
                        typeof (prop[name]) === "function" &&
                        typeof (F.prototype[name]) === "function" &&
                        /\b_super\b/.test(prop[name])) {
                            // 重定義函數name - 
                            // 首先在函數上下文設置this._super指向父類原型中的同名函數
                            // 而後調用函數prop[name],返回函數結果
                            // 注意:這裏的自執行函數建立了一個上下文,這個上下文返回另外一個函數,
                            // 此函數中能夠應用此上下文中的變量,這就是閉包(Closure)。
                            // 這是JavaScript框架開發中經常使用的技巧。
                            F.prototype[name] = (function(name, fn) {
                                return function() {
                                    this._super = baseClass.prototype[name];
                                    return fn.apply(this, arguments);
                                };
                            })(name, prop[name]);
                        } else {
                            F.prototype[name] = prop[name];
                        }
                    }
                }
                return F;
            };
        })();
        // 通過改造的jClass
        var Person = jClass.extend({
            init: function(name) {
                this.name = name;
            },
            getName: function(prefix) {
                return prefix + this.name;
            }
        });
        var Employee = Person.extend({
            init: function(name, employeeID) {
                //  調用父類的方法
                this._super(name);
                this.employeeID = employeeID;
            },
            getEmployeeIDName: function() {
                // 注意:咱們還能夠經過這種方式調用父類中的其餘函數
                var name = this._superprototype.getName.call(this, "Employee name: ");
                return name + ", Employee ID: " + this.employeeID;
            },
            getName: function() {
                //  調用父類的方法
                return this._super("Employee name: ");
            }
        });

        var zhang = new Employee("ZhangSan", "1234");
        console.log(zhang.getName());   // "Employee name: ZhangSan"
        console.log(zhang.getEmployeeIDName()); // "Employee name: ZhangSan, Employee ID: 1234"
JUST COOL!
相關文章
相關標籤/搜索