js繼承機制詳解

想要學好一種語言,一定要了解其語言特性,因爲我們 not just coding,js本身就是一種函數式的編程語言,它不與java或者其他面向對象一樣擁有繼承機制,但是我們可以利用其留下的prototype庫進行曲線救國,這就不能不找一段經典的繼承代碼來研究一下了。以下是John Resig實現的經典繼承實現代碼,

 // 自執行的匿名函數創建一個上下文,避免引入全局變量
    (function() {
        // initializing變量用來標示當前是否處於類的創建階段,
        
        var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
        // 基類構造函數
        // 這裏的this是window,所以這整段代碼就向外界開闢了一扇窗戶 - window.Class
        this.Class = function() { };
        // 繼承方法定義
        Class.extend = function(prop) {
           
            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"  ?
                        (function(name, fn) {
                            return function() {
                                var tmp = this._super;
                                this._super = _super[name];
                                var ret = fn.apply(this, arguments);

                                return ret;
                            };
                        })(name, prop[name]) :
                        prop[name];
            }
            
            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;
        };
    })();
    var Person = Class.extend({
        name:null,
        init:function(){
            alert("init person");
        },
        alerting:function(){
            alert("Person");
        }
    })
    var Student = Person.extend({
        init:function(){
            this._super();
            alert("init");
        },
        alerting:function(){
            this._super();
            alert("Student");
        }
    })
    var p = new Student();
    p.alerting();

首先通過創建一個自執行函數避免引入全局變量,主要來分析一下Class.extend這個方法,該方法中首先將父類(也就是Class)這個類的原型prototype屬性賦給_super這個變量,initializing主要是用於控制父類實例化時,prototype = new this()時,防止進入if條件,也就是這段代碼,
if (!initializing && this.init)
                    this.init.apply(this, arguments);

是否執行接下來聲明一個prototype的變量,指向父類的一個實例,prototype = new this(),這句代碼其實是將prototype的__proto__屬性指向了父類的原型,接下來將initializing這個變量=false,是爲了志明自執行方法中的return的Class這個類在後期實例化時進入if條件,例如Person就可以。相信大家都注意到了for循環中的代碼,這就是繼承機制的核心,看這個條件語句 typeof prop[name] == "function" && typeof _super[name] == "function" ,是說明如果prop對象中的name屬性是一個function並且父類中存在同名的屬性並且同樣是function,那麼執行下面這段代碼:


(function(name, fn) {
                            return function() {
                                var tmp = this._super;
                                this._super = _super[name];
                                var ret = fn.apply(this, arguments);

                                return ret;
                            };
                        })(name, prop[name])


這段代碼是什麼意思呢,這裏面其實做了兩件事情,首先將父類的同名方法賦給了this綁定的_super屬性,然後就是將子類的方法綁定到this作用域執行,我們回過頭來看看,這段代碼是賦給了prototype[name],也就是子類的該同名方法,該方法必定是由定義該方法類的實例調用,因此this作用域指向了目標類的實例,此時,this作用域中還有一個_super屬性,該屬性此時需要注意的是該屬性不是不變的,舉例來說,var p = new Student()這個實例,當實例p調用init()函數時,this._super指向了Person的init(),當p調用alerting()時,this._super指向了Person的alerting(),當我們調用p.alerting()的時候,執行this._super()其實就是調用父類的同名方法,若typeof prop[name] == "function" && typeof _super[name] == "function"不爲true的時候,那麼prototype[name] = proto[name],這其中還有一點點不好理解,如上代碼我改動一點點,將Person類中的alerting方法變成一個屬性,那麼這個條件也不成立,就執行prototype[‘alerting’]=proto['alerting'],這就有問題了,之前我們在prototype=new this()的時候,prototype的__proto__屬性指向了父類的prototype,說明其中必定有父類中的alerting屬性,這下prototype又有了一個alerting屬性,那麼我們的對象p在調用p.alerting()的時候調用的到底是alerting方法還是alerting屬性呢,其實還是alerting()方法,因爲首先對象p會在第一個__proto__屬性中找alerting()方法,若找不到,則在下一層中尋找,若找到了該方法,就不會進入下一層的__proto__屬性中繼續尋找了,所以這裏不會找到從父類繼承下來的alerting屬性,調用的只是自己的方法。到此爲止,其實只是實現了將父類的方法屬性繼承到了我們自定義的局部變量prototype屬性中,後面我們定義的Class方法,該方法不同於之前的Class方法,

function Class() {
                // 在類的實例化時,調用原型方法init
                if (!initializing && this.init)
                    this.init.apply(this, arguments);
            }

我們將Class的prototype指向了我們定義的prototype變量,其實就是繼承了父類的方法和屬性,此時Class.constructor指向了prototype變量的構造函數,所以我們顯式地改回來,Class.constructor=Class,並且使用Class.extend=arguments.callee將Class.extend方法指向了外層的Class.extend方法,方便我們進行深度擴展,最後將Class返回。

這個意思就是無論是Person或者是Student類,本質上都是Class.extend方法內部定義的Class方法(也就是類),只是我們將父類(外層的Class,也就是調用extend方法的類)和子類(也就是prop對象)的屬性揉到了內部的Class類中,最後將Class返回給我們外部,就實現了繼承了。

以上就是該繼承機制的詳解,不知道講清楚了沒有,若有疑問或者有不對之處,敬請指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章