成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

詳解js中的繼承(二)

evin2016 / 2472人閱讀

摘要:寄生組合式繼承終于寫到最后一個繼承了,我們在之前講了種繼承方式,分別是原型鏈,借用構(gòu)造函數(shù)繼承,組合繼承,原型式繼承,寄生式繼承,其中,前三種聯(lián)系比較緊密,后面兩種也比較緊密,而我們要講的最后一種,是和組合繼承還有寄生式繼承有關(guān)系的。

前言

趁周末結(jié)束之前趕緊先把坑填上。上回我們說到了原型鏈,并且留下了幾個思考題,先把答案公布一下。

在最后一個例子里,console.log(b1.constructor),結(jié)果是什么?
答案:function A,因為b1本身沒有constructor屬性,會沿著原型鏈向上找到B prototype對象,然后再往上找到A prototype對象,此時找到了constructor屬性,也就是指向函數(shù)對象A,可參見上文最后一張圖片

B.prototype = new A();B.prototype.sayB = function(){ console.log("from B") }這兩句的執(zhí)行順序能不能交換?
答案:不能,因為我們說過了,第一句是把改寫B(tài)函數(shù)對象的prototype指向的原型對象,如果我們交換了順序,是在原先的B的原型對象上綁定了方法,然后再把指針指向新的原型對象,那新的原型對象上自然就沒有綁定sayB方法,接下來的b1.sayB()就會報函數(shù)未定義錯誤,

在最后一個例子里,A看似已經(jīng)是原型鏈的最頂層,那A還能再往上嗎?
答案,可以,因為其實所有的引用類型都默認繼承了了Object,也就是說,完整的原型鏈應(yīng)該是A prototype[Prototype]屬性指向Object prototype。如圖:


順便補充一下,Object prototype上的原生方法,包括我們常用的hasOwnProperty()、isPropertyOf()等。

接著談繼承

在上一篇我們講解了原型鏈的原理,建議沒有理解清楚的讀者朋友先理解之前的知識點,避免難點疊加

原型鏈的缺陷

引用類型的值在原型鏈傳遞中存在的問題
我們知道js中有值類型和引用類型,其中引用類型包括Object.Array等,引用類型的值有一個特點:在賦值的時候,賦給變量的是它在內(nèi)存中的地址。換句話說,被賦值完的變量相當(dāng)于一個指針,這會有什么問題呢?看例子:

   function A() {
        this.name = "a" 
        this.color = ["red","green"];         
    }
    function B(){

    }
     //讓B的原型對象指向A的一個實例
     B.prototype = new A();
     
     //生成兩個個B的實例
     var b1 = new B();
     var b2 = new B();
     //觀察color屬性
     console.log(b1.name)//a
     console.log(b2.name)//a
     console.log(b1.color)//[red,green]
     console.log(b2.color)//[red,green]
     //改變b1的name和color屬性
     b1.name = "b"
     b1.color.push("black")
     
     //重新觀察color屬性
     console.log(b1)//b
     console.log(b2)//a
     console.log(b2.name)
     console.log(b1.color)//["red", "green", "black"]
     console.log(b2.color)//["red", "green", "black"]

發(fā)現(xiàn)問題了嗎?我們修改了b1的color和name屬性,但是b2name屬性不變,color屬性發(fā)生了改變。為了搞清楚這里問題,請嘗試回答我的問題(想不出來的話,可以自己通過在控制臺打印出來驗證):

b1b2有自己的color屬性嗎?
答案:沒有,只是B prototype上有color屬性,因為它是A的一個實例,b1b2其實是通過[Proto]屬性訪問B prototype上的color屬性(指針),從而訪問和操作color數(shù)組的;

b1b2有自己的name屬性嗎?
答案:一開始都沒有,當(dāng)執(zhí)行了b1.name = "b"時,相當(dāng)于b1有了自己的name屬性,而b2依然沒有name屬性。

所以以上問題的原因來源就是我們前面說的:引用類型的值在賦值的時候,賦給變量的是它在內(nèi)存中的地址。(如果關(guān)于值類型和引用類型有沒掌握的同學(xué)可以先去看看或者私下問我,這里默認這個是已經(jīng)了解的。)
所以在原型鏈中如果A(其實就是繼承中的父類型)含有引用類型的值,那么子類型的實例共享這個引用類型得值,也就是上面的color數(shù)組,這就是原型鏈的第一個缺陷。

第二個缺陷是:在創(chuàng)建子類型的實例(如b1,b2)時,無法向父類型的構(gòu)造函數(shù)中傳遞參數(shù)。比如在上面的例子中,如果Aname屬性是要傳遞參數(shù)的而不是寫死的,那么我們在實例化b1b2的時候根本沒法傳參

借用構(gòu)造函數(shù)繼承

為了解決引用類型值帶來的問題,我們會采用借用構(gòu)造函數(shù)繼承的方式,又名*偽造對象或者經(jīng)典繼承,核心思路是:我們在子類型的構(gòu)造函數(shù)中調(diào)用父類型的構(gòu)造函數(shù),這里要用到一個方法call()或者apply()函數(shù),關(guān)于這個函數(shù),我這里簡單介紹一下,可以簡單的理解功能就是,允許一個對象調(diào)用另一個對象的方法。具體的作用如果大家覺得需要可以在評論區(qū)回復(fù),我會后面多帶帶寫一下這兩個函數(shù)。在這里就不展開了。具體實現(xiàn)如下:

        function A() {
            this.name = "a" 
            this.color = ["red","green"];         
        }
        function B(){
          //“借用”|就體現(xiàn)在這里,子類型B借用了父類型A的構(gòu)造函數(shù),從而在這里實現(xiàn)了繼承
          A.call(this);
        }
       
         
         //生成兩個個B的實例
         var b1 = new B();
         var b2 = new B();
         //觀察color屬性
         console.log(b1.name)//a
         console.log(b2.name)//a
         console.log(b1.color)//["red","green"]
         console.log(b2.color)//["red","green"]
         //改變b1的name和color屬性
         b1.name = "b"
         b1.color.push("black")
         
         //重新觀察屬性
         console.log(b1.name)//b
         console.log(b2.name)//a
         console.log(b1.color)//["red","green","black"]
         console.log(b2.color)//["red", "green"]

在這里我們沒有采用原型鏈,而是利用call()方法來實現(xiàn)在子類型的構(gòu)造函數(shù)中借用父類型的構(gòu)造函數(shù),完成了繼承,這樣繼承的結(jié)果就是:b1,b2都分別擁有自己的namecolor屬性(可以直接console.log(b1)查看對象的屬性),也就是b1b2完全獨立的。這就解決了之前的第一個問題,而且傳遞參數(shù)的問題其實也可以解決,再稍微改一下A函數(shù):

  //這里name改成傳遞參數(shù)的
        function A(name) {
            this.name = name 
            this.color = ["red","green"];         
        }
        function B(name){
          //在這里我們接受一個參數(shù),并且通過call方法傳遞到A的構(gòu)造函數(shù)中
          A.call(this,name);
        }
       
         
         //生成兩個個B的實例
         var b1 = new B("Mike");
         var b2 = new B("Bob");
         //觀察屬性
         console.log(b1.name)//Mike
         console.log(b2.name)//Bob
         console.log(b1.color)//["red","green"]
         console.log(b2.color)//["red","green"]
      

其實上面就可以直接寫成這樣,但是為了讓大家更容易理解,故意分開,隔離變量(大家看我這么用心真的不考慮點個贊嗎?),順便再解釋一下A.call(this,name);就是讓this對象(這里是指B)調(diào)用構(gòu)造函數(shù)A,同時傳入一個參數(shù)name。

可以看到,借用構(gòu)造函數(shù)繼承不會有原型鏈繼承的問題,那為什么不都借用采用構(gòu)造函數(shù)繼承的方法呢?原因在于:這種繼承方式,所有的屬性和方法都要在構(gòu)造函數(shù)中定義,比如我們這里也要綁定之前的sayA()方法并繼承,就只能寫在A的構(gòu)造函數(shù)里面,而寫在A prototype的的方法,沒法通過這種方式繼承,而把所有的屬性和方法都要在構(gòu)造函數(shù)中定義的話,就不能對函數(shù)方法進行復(fù)用.

組合繼承

學(xué)習(xí)了原型鏈的繼承和借用構(gòu)造函數(shù)的繼承后,我們可以發(fā)現(xiàn),這兩種方法的優(yōu)缺點剛好互補:

原型鏈繼承可以把方法定義在原型上,從而復(fù)用方法

借用構(gòu)造函數(shù)繼承法可以解決引用類型值的繼承問題和傳遞參數(shù)問題

因此,就自然而然的想到,結(jié)合這兩種方法,于是就有了下面的組合繼承,也叫偽經(jīng)典繼承,(前面的借用構(gòu)造函數(shù)是經(jīng)典繼承,可以聯(lián)系起來),具體實現(xiàn)如下:

    function A(name) {
            this.name = name 
            this.color = ["red","green"];     
        }
        A.prototype.sayA = function(){
          console.log("form A")
        }
        function B(name,age){
          //借用構(gòu)造函數(shù)繼承
          A.call(this,name);
          this.age = age;
        }

        //原型鏈
        B.prototype = new A();
        B.prototype.sayB = function(){
          console.log("form B")
        }
         
         //生成兩個個B的實例
         var b1 = new B("Mike",12);
         var b2 = new B("Bob",13);
         //觀察color屬性
         console.log(b1)//{name:"Mike"...}
         console.log(b2)//{name:"Bob"...}
         b1.sayA()//from A
         b2.sayB()//from B

這個例子只是對上面的例子稍作修改:

我們在A prototype上定義了sayA() ,在B prototype 定義了sayB()

我們增加了B.prototype = new A();原型鏈

最終實現(xiàn)的效果就是,b1和b2都有各自的屬性,同時方法都定義在兩個原型對象上,這就達到了我們的目的:屬性獨立,方法復(fù)用,這種繼承的理解相對簡單,因為就是把前兩種繼承方式簡單的結(jié)合一下,原型鏈負責(zé)原型對象上的方法,call借用構(gòu)造函數(shù)負責(zé)讓子類型擁有各自的屬性。
組合繼承是js中最常用的繼承方式

原型式繼承

原型式繼承與之前的繼承方式不太相同,原理上相當(dāng)于對對象進行一次淺復(fù)制,淺復(fù)制簡單的說就是:把父對像的屬性,全部拷貝給子對象。但是我們前面說到,由于引用類型值的賦值特點,所以屬性如果是引用類型的值,拷貝過去的也僅僅是個指針,拷貝完后父子對象的指針是指向同一個引用類型的(關(guān)于深復(fù)制和淺復(fù)制如果需要細講的同樣可以在評論區(qū)留言。)原型式繼承目前可以通過Object.create()方式來實現(xiàn),(這個函數(shù)的原理我不想在這里提,因為我希望讀者在看完這里內(nèi)容以后自己去查閱一下這個內(nèi)容)本文只講實現(xiàn)方式:
Object.create()接收兩個參數(shù):

第一個參數(shù)是作為新對象的原型的對象

第二個參數(shù)是定義為新對象增加額外屬性的對象(這個是可選屬性)

如果沒有傳遞第二個參數(shù)的話,就相當(dāng)于直接運行object()方法(這個方法如果不懂直接百度就好)
上面的說法可能有點拗口,換句話說:

比如說我們現(xiàn)在要創(chuàng)建一個新對象B,那么要先傳入第一個參數(shù)對象A,這個A將被作為B prototype;然后可以再傳入一個參數(shù)對象C,C對象中可以定義我們需要的一些額外的屬性。來看例子

    var A  = {
        name:"A",
        color:["red","green"]
    }

    //使用Object.create方法先復(fù)制一個對象
    var B = Object.create(A);
    B.name = "B";
    B.color.push("black");

    //使用Object.create方法再復(fù)制一個對象
    var C = Object.create(A);
    C.name = "C";
    B.color.push("blue");
    console.log(A.name)//A
    console.log(B.name)//B
    console.log(C.name)//C
    console.log(A.color)//["red", "green", "black", "blue"]
         

在這個例子中,我們只傳入第一個參數(shù),所以BC都是對A淺復(fù)制的結(jié)果,由于name是值類型的,color是引用類型的,所以ABC的name值獨立,color屬性指向同一個對象。接下來舉個傳遞兩個參數(shù)的例子:

    var A  = {
        name:"A",
        color:["red","green"],
        sayA:function(){
            console.log("from A");
        }
    };

    //使用Object.create方法先復(fù)制一個對象
    var B = Object.create(A,{
        name:{
          value:"B"
        }
    });
    console.log(B)//Object{name:"B"}
    B.sayA()//"from A"

這個例子就很清楚的表明了這個函數(shù)的作用了,傳入的A對象被當(dāng)做B的原型,所以生成B對象沒有sayA()方法,卻可以調(diào)用該方法(類似于通過原型鏈),同時我們在第二個參數(shù)中修改了B自己的name,所以就實現(xiàn)了這種原型式繼承。原型式繼承的好處是:如果我們只是簡單的想保持一個對象和另一個對象類似,不必大費周章寫一堆代碼,直接調(diào)用就能實現(xiàn)

寄生式繼承

寄生式繼承和原型繼承聯(lián)系緊密,思路類似于工廠模式,即創(chuàng)建一個只負責(zé)封裝繼承過程的函數(shù),在函數(shù)中根據(jù)需要增強對象,最后返回對象

 function createA(name){
    //創(chuàng)建新對象
    var obj = Object(name);
    //增強功能
     obj.sayO = function(){
         console.log("from O")
     };
    //返回對象
    return obj;
     
}
var A = {
    name:"A",
    color:["red","green","blue"]
};
//實現(xiàn)繼承
var  B = createA(A);
console.log(B)//Object {name: "A", color: Array[3]}
B.sayO();//from O

繼承的結(jié)果是B擁有A的所有屬性和方法,而且具有自己的sayO()方法,效果和原型式繼承很相似,讀者可以比較一下寄生式繼承和原型式繼承的相似和區(qū)別。

寄生組合式繼承

終于寫到最后一個繼承了,我們在之前講了5種繼承方式,分別是原型鏈,借用構(gòu)造函數(shù)繼承,組合繼承,原型式繼承,寄生式繼承,其中,前三種聯(lián)系比較緊密,后面兩種也比較緊密,而我們要講的最后一種,是和組合繼承還有寄生式繼承有關(guān)系的。(看名字就知道了嘛)

友情提示:如果看到這里有點累的讀者可以先休息一下,因為雖然已經(jīng)分了一二兩篇,本文的篇幅還是稍長(我都打了兩個多小時了),而且如果先把之前的理解清楚,比較容易理解最后一種繼承。

組合繼承仍有缺陷

我們在之前說過,最常用的繼承方式就是組合繼承,但是看似完美的組合繼承依然有缺點:子類型會兩次調(diào)用父類型的構(gòu)造函數(shù),一次是在子類型的構(gòu)造函數(shù)里,另一次是在實現(xiàn)原型鏈的步驟,來看之前的代碼:

    function A(name) {
            this.name = name 
            this.color = ["red","green"];     
        }
        A.prototype.sayA = function(){
          console.log("form A")
        }
        function B(name,age){
         //第二次調(diào)用了A
          A.call(this,name);
          this.age = age;
        }

        //第一次調(diào)用了A
        B.prototype = new A();
        B.prototype.sayB = function(){
          console.log("form B")
        }
         

         var b1 = new B("Mike",12);
         var b2 = new B("Bob",13);
          console.log(B.prototype)//A {name: undefined, color: Array[2]}
       

在第一次調(diào)用的時候,生成了B.prototype對象,它具有namecolor屬性,因為它是A的一個實例;第二次調(diào)用的時候,就是實例化b1b2的時候,這時候b1b2也具有了namecolor屬性,我們之前說過,原型鏈的意義是:當(dāng)對象本身不存在某個屬性或方法的時候,可以沿著原型鏈向上查找,如果對象自身已經(jīng)有某種屬性或者方法,就訪問自身的,但是我們現(xiàn)在發(fā)現(xiàn),通過組合繼承,只要是A里面原有的屬性,B prototype對象一定會有,b1b2肯定也會有,這樣就造成了一種浪費:B prototyope上的屬性其實我們根本用不上,為了解決這個問題,我們采用寄生組合式繼承。
寄生組合式繼承的核心思路是其實就是換一種方式實現(xiàn) B.prototype = new A();從而避免兩次調(diào)用父類型的構(gòu)造函數(shù),官方定義是:使用寄生式繼承來繼承父類型的原型,然后將結(jié)果指定給子類型的原型,。`這句話不容易理解,來看例子:

//我們一直默認A是父類型,B是子類型
function inheritPrototype(B,A){
    //復(fù)制一個A的原型對象
    var pro  = Object(A.prototype);
    //改寫這個原型對象的constructor指針指向B
    pro.constructor = B;
    //改寫B(tài)的prototype指針指向這個原型對象
    B.prototype = pro;
}

這個函數(shù)很簡短,只有三行,函數(shù)內(nèi)部發(fā)生的事情是:我們復(fù)制一個A的原型對象,然后把這個原型對象替換掉B的原型對象。為什么說這樣就代替了 B.prototype = new A();,不妨思考一下,我們最初為什么要把B的prototype屬性指向A的一個實例?無非就是想得到A的prototype的一個復(fù)制品,然后實現(xiàn)原型鏈。而現(xiàn)在我們這樣的做法,同樣達到了我們的母的目的,而且,此時B的原型對象上不會再有A的屬性了,因為它不是A的實例。因此,只要把將上面的 B.prototype = new A();,替換成inheritPrototype(B,A),就完成了寄生組合式繼承。

寄生組合式繼承保持了組合繼承的優(yōu)點,又避開了組合繼承會有無用屬性的缺陷,被認為是最理想的繼承方式。

小結(jié)

終于寫完了??! 明天還得起早去上班,下一次更新可能會放在這一周的周末。關(guān)于這一篇內(nèi)容,建議的閱讀方式是先讀前三種繼承方式,再看后兩種繼承,都理解的差不多了,就可以看最后一種繼承方式了。中間注意消化和休息。最后再提一下吧:如果喜歡本文,請大方的點一下右上角的推薦和收藏(反正你們還是喜歡只收藏不推薦),雖然說寫這個一方面是為了自己鞏固知識,但是為了讓讀者更容易理解,我盡量都是采用拆解的方式來講,而且穿插了新知識的時候都會給出解釋,并不是直接搬運書本知識過來,那樣毫無意義。這么做還是希望寫的文章能夠更有價值,讓更多人能夠得到幫助!以上內(nèi)容屬于個人見解,如果有不同意見,歡迎指出和探討。請尊重作者的版權(quán),轉(zhuǎn)載請注明出處,如作商用,請與作者聯(lián)系,感謝!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82096.html

相關(guān)文章

  • javaScript原型及原型鏈詳解

    摘要:當(dāng)然這還沒完,因為我們還有重要的一步?jīng)]完成,沒錯就是上面的第行代碼,如果沒有這行代碼實例中的指針是指向構(gòu)造函數(shù)的,這樣顯然是不對的,因為正常情況下應(yīng)該指向它的構(gòu)造函數(shù),因此我們需要手動更改使重新指向?qū)ο蟆? 第一節(jié)內(nèi)容:javaScript原型及原型鏈詳解(二) 第一節(jié)中我們介紹了javascript中的原型和原型鏈,這一節(jié)我們來講利用原型和原型鏈我們可以做些什么。 普通對象的繼承 ...

    widuu 評論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...

    jsbintask 評論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...

    muddyway 評論0 收藏0
  • JavaScript深入淺出

    摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...

    blair 評論0 收藏0

發(fā)表評論

0條評論

evin2016

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<