詳解js中的繼承(一)

前言

最近在學vue,到週末終於有空寫一些東西了(想一想又能騙贊,就有點小激動!)。在javascript基礎中,除了閉包以外,繼承也是一個難點。由於考慮到篇幅較長,因此打算分紅兩個部分來寫。一樣基於《javascript高級程序設計》,作一個詳細的講解,若是有不對的地方歡迎指正。javascript

準備知識

爲了更好的講解繼承,先把一些準備知識放在前面。vue

1.構造函數,實例

構造函數,是用來建立對象的函數,本質上也是函數。與其餘函數的區別在於調用方式不一樣:java

  • 若是經過new操做符來調用的,就是構造函數瀏覽器

  • 若是沒有經過new操做符來調用的,就是普通函數
    例子:閉包

    function Person(name, age) {
       this.name = name;
       this.age = age;
     }
     //當作構造函數調用
     var person1 = new Person('Mike',10);
     
     //當作普通函數調用,這裏至關於給window對象添加了name和age屬性,這個不是重點,只要注意調用方式
     Person('Bob',12);
     
     console.log(person1)//Person {name: "Mike", age: 10}
     console.log(name)//Bob
     console.log(age)//12

var person1 = new Person('Mike',10);中,經過new操做符調用了函數Person,而且生成了person1,
這裏的Person就稱爲構造函數person1稱爲Person函數對象的一個實例。實能夠經過實例的constructor訪問對應的構造函數(可是其實上這個constructor不是實例的屬性,後面會解釋爲何),看下面的例子:函數

function Person(name, age) {
    this.name = name;
    this.age = age;
  }
 var person1 = new Person('Mike',10);
 var person2 = new Person('Alice',20);
 console.log(person1.constructor)//function Person(){省略內容...}
 console.log(person2.constructor)//function Person(){省略內容...}

2.原型對象

當咱們每次建立一個函數的時候,函數對象都會有一個prototype屬性,這個屬性是一個指針,指向它的原型對象原型對象的本質也是一個對象。初次看這句話可能有點難以理解,舉個例子,仍是剛剛那個函數:工具

function Person(name, age) {
        this.name = name;
        this.age = age;
     }
     console.log(Person.prototype)//object{constructor:Person}

能夠看到Person.prototype指向了一個對象,即Person的原型對象,而且這個對象有一個constructor屬性,又指向了Person函數對象。是否是有點暈?不要緊,接下來咱們就上比舉例子更好的手段--畫圖。測試

3.構造函數,原型對象和實例的關係

在前面,咱們剛剛介紹過了構造函數,實例和原型對象,接下來咱們用一張圖來表示這三者之間的關係(用ps畫這種圖真是麻煩的要死,你們有好的工具推薦一下):
關係圖
從圖上咱們能夠看到:this

  • 函數對象的prototype指向原型對象,原型對象的constructor指向函數對象spa

  • 實例對象的[Protoptype]屬性指向原型對象,這裏的[Protoptype]內部屬性,能夠先理解爲它是存在的,可是不容許咱們訪問(雖然在有些瀏覽器是容許訪問這個屬性的,可是咱們先這樣理解),這個屬性的做用是:容許實例經過該屬性訪問原型對象中的屬性和方法。好比說:

function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      //在原型對象中添加屬性或者方法
     Person.prototype.sex = '男'; 
     var person1 = new Person('Mike',10);
     var person2 = new Person('Alice',20);
     //只給person2設置性別
     person2.sex = '女';
     console.log(person1.sex)//'男'
     console.log(person2.sex)//'女'

這裏咱們沒有給person1實例設置sex屬性,可是由於[Protoptype]的存在,會訪問原型對象中對應的屬性;
同時咱們給person2設置sex屬性後輸出的是'女',說明只有當實例自己不存在對應的屬性或方法時,纔會去找原型對象上的對應屬性或方法

  • 補充一下:ECMA-262第五版的時候這個內部屬性叫[Prototype],而_proto_Firefox,Chrome和Safari瀏覽器提供的一個屬性,在其餘的實現裏面,這個內部屬性是無法訪問的。因此咱們能從控制檯看到的是_proto_屬性,可是我在文中用的仍是[Prototype],我的認爲這樣較符合它的本質。

  • tips:這裏恰好解釋一下console.log(person1.constructor)時,說到的,能夠經過實例的constructor訪問構造函數,可是constructor本質上是原型對象的屬性。

繼承

原型鏈

在js中,繼承的主要思路就是利用原型鏈,所以若是理解了原型鏈,繼承問題就理解了一半。在這裏能夠稍微休息一下,若是對前面的準備知識已經理解差很少了,就開始講原型鏈了。

原型鏈的原理是:讓一個引用類型繼承另外一個引用類型的屬性和方法。
先回顧一下剛剛講過的知識:

  • 原型對象經過constructor屬性指向構造函數

  • 實例經過[Prototype]屬性指向原型對象

那如今咱們來思考一個問題:若是讓原型對象等於另外一個構造函數的實例會怎麼樣?
例如:

function A() {
     
    }
    //在A的原型上綁定sayA()方法
    A.prototype.sayA = function(){
            console.log("from A")
    }
    function B(){

    }
    
     //讓B的原型對象指向A的一個實例
     B.prototype = new A();
     
     //在B的原型上綁定sayB()方法
     B.prototype.sayB = function(){
            console.log("from B")
     }
     //生成一個B的實例
     var a1 = new A();
     var b1 = new B();
     
     //b1能夠調用sayB和sayA
     b1.sayB();//'from B'
     b1.sayA();//'from A'

爲了方便理解剛剛發生了什麼,咱們再上一張圖:
原型鏈
如今結合圖片來看代碼:

  • 首先,咱們建立了A和B兩個函數對象,同時也就生成了它們的原型對象

  • 接着,咱們給A的原型對象添加了sayA()方法
    * 而後是關鍵性的一步B.prototype = new A();,咱們讓函數對象B的protytype指針指向了一個A的實例,請注意個人描述:是讓函數對象B的protytype指針指向了一個A的實例,這也是爲何最後,B的原型對象裏面再也不有constructor屬性,其實B原本有一個真正的原型對象,本來能夠經過B.prototype訪問,可是咱們如今改寫了這個指針,使它指向了另外一個對象,因此B真正的原型對象如今無法被訪問了,取而代之的這個新的原型對象是A的一個實例,天然就沒有constructor屬性了

  • 接下來咱們給這個B.prototype指向的對象,增長一個sayB方法

  • 而後,咱們生成了一個實例b1

  • 最後咱們調用了b1的sayB方法,能夠執行,爲何?
    由於b1有[Prototype]屬性能夠訪問B prototype裏面的方法;

  • 咱們調用了b1的sayA方法,能夠執行,爲何?
    由於b1沿着[Prototype]屬性能夠訪問B prototype,B prototype繼續沿着[Prototype]屬性訪問A prototype,最終在A.prototype上找到了sayA()方法,因此能夠執行

因此,如今的結果就至關於,b1繼承了A的屬性和方法,這種[Prototype]不斷把實例和原型對象聯繫起來的結構就是原型鏈。也是js中,繼承主要的實現方式。

小結

由於這部分知識理解起來比較難,因此第一部分先寫到這裏(固然不是由於我想多寫一篇來騙贊和關注啦),你們讀到這裏也能夠歇口氣了,若是這一塊理解深入,下一部分就會很輕鬆。
爲了測試一下你們對於本文的理解程度,問一下幾個問題:

  1. 在最後一個例子裏,console.log(b1.constructor),結果是什麼?

  2. B.prototype = new A(); B.prototype.sayB = function(){ console.log("from B") }這兩句的執行順序能不能交換

  3. 最後再思考一下. 在最後一個例子裏,A看似已是原型鏈的最頂層,那A還能再往上嗎?

以上答案在下篇中解答,讀者能夠本身先試試,思考一下,有疑問也能夠在評論中提出。最後,若是這篇文章對你有幫助,請大方的點收藏和推薦吧(每次都是收藏比推薦多!,組織語言,畫圖和排版都很辛苦的),大家的支持會給我更大的動力~以上內容屬於我的看法,若是有不一樣意見,歡迎指出和探討。請尊重做者的版權,轉載請註明出處,如做商用,請與做者聯繫,感謝!

相關文章
相關標籤/搜索