JavaScript繼承詳解(五)

來源:互聯網
上載者:User

 

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

調用方式

調用方式非常優雅: 注意:代碼中的Class、extend、_super都是自訂的對象,我們會在後面的程式碼分析中詳解。

        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是一個Regex,可能的取值為(/\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!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.