js繼承的實現(原型/鏈、函數偽裝)


一、原型繼承父類的實例

        //父類及其原型屬性/方法
        function SuperType () {
            this.name = ['zc','ls','ww'];
        }
        SuperType.prototype.getSuperName = function() {
            return this.name;
        };


        //子類及其原型屬性/方法
        function SubType() {
            this.test = ['a','b','c','d'];
        }
        //子類型的原型指向父類型的實例(即子類的原型復制了父類的構造器以及父類原型屬性/方法)
        SubType.prototype = new SuperType(); //為子類原型添加原型拓展屬性/方法
        SubType.prototype.getSubTest = function() {
            return this.test;
        }

        var instance1 = new SubType();
        instance1.name.push('yzy');//name屬性是原型繼承自父類實例
        instance1.test.push('e');//test屬性是源於子類本身的構造器
        console.log(instance1.name,instance1.test)

        var instance2 = new SubType();
        console.log(instance2.name,instance2.test)

控制台輸出:

標注:

注意這里的子類原型指向一個父類的實例(引用傳遞),那么這塊的父類實例就是內存中的一塊地址,以后所有的子類實例都會有一個原型屬性指向這塊地址,並且子類A對這塊地址中數據更改也會影響到子類B。

圖示:

 

所以你可以看到,instance1.name是從父類實例來的,這個屬性實際存在於這個單例,訪問的時候都是引用傳遞,由於這個單例是共享的,instance1 push了一個數據,那么就算instance2沒有任何動作,instance2讀的時候數據也會是變化后的數據;

而對於test屬性,是子類自身的,所以這個屬性值存在於子類實例自身,相互之間互不影響,所以雖然instance1.test push了一個數據,但是instance2訪問的時候絲毫不受影響。

缺點:繼承自父類實例的原型屬性會被所有實例所共享。

二、構造函數偽裝(call()、apply())

        //父類及其原型屬性/方法
        function SuperType(name) {
            this.name = name;
            this.color = ['green','red'];
        }
        SuperType.prototype.getSuperName = function() {
            return this.name;
        }

        //子類及其原型屬性/方法
        function SubType(name) {
            SuperType.call(this, name);this.test = ['a','b','c','d'];
        }
        SubType.prototype.getSubTest = function() {
            return this.test;
        }

        var instance1 = new SubType('Jack');
        console.log(instance1.name,instance1.getSubTest());
        console.log('------------------------');
        console.log(instance1.getSuperName())

控制台輸出:

標注:

call()方法實際上就是在當前作用域拷貝了一下函數執行者的構造函數/方法,所以上述call()方法實際上做了如下的事情

        //子類及其原型屬性/方法
        function SubType(name) {
            //SuperType.call(this, name);
            this.name = name;
            this.color = ['green','red'];
            
            this.test = ['a','b','c','d'];
        }

注意的是,call()函數偽裝並不會在當前作用域執行 SuperType 原型下的方法/屬性

所以,因為 getSuperName() 是父類原型下的方法,所以call() 方法自然不會復制該方法給 SubType 構造器,因此控制台報錯也就是理所當然的咯

缺點:函數偽裝不會繼承父類原型下的屬性/方法。

三、組合繼承(函數偽裝 + 原型繼承)

        //父類及其原型屬性/方法
        function SuperType(name) {
            this.name = name;
        }
        SuperType.prototype.getSuperName = function () {
            return this.name;
        }

        // 子類1及其原型屬性/方法
        function SubType1(name) {
            SuperType.call(this, name);
            this.test = ['h1', 'h2', 'h3', 'h4'];
        }
        SubType1.prototype = SuperType.prototype;
        SubType1.prototype.getSubTest = function () {
            return this.test;
        }


        // 子類2及其原型屬性/方法
        function SubType2(name) {
            SuperType.call(this, name);
            this.age = 18;
        }
        SubType2.prototype = SuperType.prototype;
        SubType2.prototype.getSubAge = function () {
            return this.age;
        }

        var instance1 = new SubType1('Jack');
        var instance2 = new SubType2('Tom');
        console.log(instance1,instance2);

控制台輸出:

標注:

①這里子類原型繼承自父類原型,然后子類為原型添加了原型拓展,這里的原型繼承是引用傳遞,所以添加拓展的操作都是基於同一塊內存地址的。

圖示:

所以,無論是父類的原型屬性還是子類繼承的原型(父類原型),實際上都是引用傳遞,都指向內存中的同一塊地址,因此,上述的代碼,雖然子類2雖然沒有原型方法 getSubTest,但是實際上子類1已經在他們指向的共同內存地址添加了該方法,同理子類1也是。

缺點:子類型的原型屬性共享。

四、寄生組合式繼承

        function object(o) {
            function F() { };
            F.prototype = o;
            return new F();
        }
        //寄生組合式繼承 
        function inheritPrototype(subType, superType) {
            var prototype = object(superType.prototype);
            subType.prototype = prototype;
        }

        //父類及其原型屬性/方法
        function SuperType(name) {
            this.name = name;
        }
        SuperType.prototype.getSuerperName = function () {
            return this.name;
        }

        //子類1及其原型屬性/方法
        function SubType(name) {
            SuperType.call(this, name);
            this.test = ['h1', 'h2', 'h3', 'h4'];
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.getSubTest = function () {
            return this.test;
        };

        //子類2及其原型屬性/方法
        function SubType2(name) {
            SuperType.call(this, name);
            this.test2 = ['s1', 's2', 's3', 's4'];
        }
        inheritPrototype(SubType2, SuperType);
        SubType2.prototype.getSubTest2 = function () {
            return this.test2;
        };

        /* 以下為測試代碼示例 */
        var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
        var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
        console.log(instance1,instance2)

控制台輸出:

標注:

我們看這個寄生組合式繼承的處理方式,傳進來一個子類和父類,子類的原型 = 新對象(新對象的原型 = 父類的原型),所以就是子類原型下的原型 = 父類的原型

這就是我們所看到的上面控制台輸出的結果了,父類的原型掛在子類原型下的原型下,這樣為各個子類添加原型的時候就不會影響掛在上面的父類原型了。

但是,由於依舊是引用傳遞,所以這個子類原型下原型(繼承自父類的原型)依舊是共享的

圖示:

為達上述目的,我這邊直接將父類實例掛在子類原型上,也是可以的:

        //寄生組合式繼承
        function inheritPrototype(subType, superType) {
            subType.prototype =new superType();
        }

        //父類及其原型屬性/方法
        function SuperType(name) {
            if(name){
                this.name = name;
            }
        }
        SuperType.prototype.getSuerperName = function () {
            return this.name;
        }
        
        //子類1及其原型屬性/方法
        function SubType(name) {
            SuperType.call(this, name);
            this.test = ['h1', 'h2', 'h3', 'h4'];
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.getSubTest = function () {
            return this.test;
        };

        //子類2及其原型屬性/方法
        function SubType2(name) {
            SuperType.call(this, name);
            this.test2 = ['s1', 's2', 's3', 's4'];
        }
        inheritPrototype(SubType2, SuperType);
        SubType2.prototype.getSubTest2 = function () {
            return this.test2;
        };

        /* 以下為測試代碼示例 */
        var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
        var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
        console.log(instance1,instance2)

標注:

這里掛載在子類原型下的原型的是一個父類的實例,值得注意的是,實例化一個父類實例是會自動調用父類構造器的,所以會將父類構造器以及父類原型一同掛載到子類原型下的原型下,不妨讓我們把上述例子中的父類構造器if判斷去掉看看控制台輸出結果:

講到這里你是不是覺得已經結束了???當然~~~沒有!

上面說過:這個子類原型下原型(繼承自父類的原型)依舊是共享的!

那么我后來做了個實驗:

Ⅰ.父類原型屬性值是基本數據類型

        //寄生組合式繼承
        function inheritPrototype(subType, superType) {
            subType.prototype =new superType();
        }

        //父類及其原型屬性/方法
        function SuperType(name) {
            if(name){
                 this.name = name;
            }
        }
        SuperType.prototype.getSuerperName = function () {
            return this.name;
        }
 
         

        SuperType.prototype.age = 12 SuperType.prototype.console = function(){ this.age += 1; console.log(this.age) };

//子類1及其原型屬性/方法
        function SubType(name) {
            SuperType.call(this, name);
            this.test = ['h1', 'h2', 'h3', 'h4'];
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.getSubTest = function () {
            return this.test;
        };

        //子類2及其原型屬性/方法
        function SubType2(name) {
            SuperType.call(this, name);
            this.test2 = ['s1', 's2', 's3', 's4'];
        }
        inheritPrototype(SubType2, SuperType);
        SubType2.prototype.getSubTest2 = function () {
            return this.test2;
        };

        /* 以下為測試代碼示例 */
        var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
        var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
        instance1.console(); instance1.console(); instance1.console(); instance1.console(); instance1.console(); instance2.console();

控制台輸出:

結果說明:父類原型下的age屬性沒有共享!

Ⅱ.父類原型屬性值是非基本數據類型(例如:對象):

        //寄生組合式繼承
        function inheritPrototype(subType, superType) {
            subType.prototype =new superType();
        }

        //父類及其原型屬性/方法
        function SuperType(name) {
            if(name){
                 this.name = name;
            }
        }
        SuperType.prototype.getSuerperName = function () {
            return this.name;
        }

 SuperType.prototype.age = { age:12 } SuperType.prototype.console = function(){ this.age.age += 1; console.log(this.age.age) };

//子類1及其原型屬性/方法
        function SubType(name) {
            SuperType.call(this, name);
            this.test = ['h1', 'h2', 'h3', 'h4'];
        }
        inheritPrototype(SubType, SuperType);
        SubType.prototype.getSubTest = function () {
            return this.test;
        };

        //子類2及其原型屬性/方法
        function SubType2(name) {
            SuperType.call(this, name);
            this.test2 = ['s1', 's2', 's3', 's4'];
        }
        inheritPrototype(SubType2, SuperType);
        SubType2.prototype.getSubTest2 = function () {
            return this.test2;
        };

        /* 以下為測試代碼示例 */
        var instance1 = new SubType(['yyy', 'Jack', 'Nick']);
        var instance2 = new SubType2(['yyy2', 'Jack2', 'Nick2']);
        instance1.console(); instance1.console(); instance1.console(); instance1.console(); instance1.console(); instance2.console();

控制台輸出:

結果說明:父類原型下的age屬性共享!

 

綜上所述:

  原型上的基本數據類型屬性是值傳遞(內存地址不共享);

  原型上的非基本數據類型屬性是引用傳遞(內存地址共享)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM