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

1、原型繼承父類的實例app

        //父類及其原型屬性/方法
        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。this

圖示:spa

 

因此你能夠看到,instance1.name是從父類實例來的,這個屬性實際存在於這個單例,訪問的時候都是引用傳遞,因爲這個單例是共享的,instance1 push了一個數據,那麼就算instance2沒有任何動做,instance2讀的時候數據也會是變化後的數據;prototype

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

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

2、構造函數假裝(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())

控制檯輸出:blog

標註:

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 構造器,所以控制檯報錯也就是理所固然的咯

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

3、組合繼承(函數假裝 + 原型繼承)

        //父類及其原型屬性/方法
        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也是。

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

4、寄生組合式繼承

        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屬性共享!

 

綜上所述:

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

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

相關文章
相關標籤/搜索