首頁  >  文章  >  web前端  >  詳細解讀js中的繼承機制

詳細解讀js中的繼承機制

阿神
阿神原創
2017-10-24 09:16:251423瀏覽

前言

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

準備知識

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

1.建構函數,實例

建構函數,是用來建立物件的函數,本質上也是函數。與其他函數的差別在於呼叫方式不同:

  • 如果透過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屬性,指向對應的建構子,看下面的例子:

 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畫這種圖真是麻煩的要死,大家有好的工具推薦一下):
詳細解讀js中的繼承機制
#從圖上我們可以看到:

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

  • 實例物件的 [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屬性後輸出的是'女',說明只有當實例本身不存在對應的屬性或方法時,才會去找原型物件上的對應屬性或方法

繼承

原型鏈

在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'

為了方便理解剛剛發生了什麼,我們再上一張圖:


##現在結合圖片來看程式碼:詳細解讀js中的繼承機制

    首先,我們創建了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中,繼承主要的實作方式。

原文出自於https://segmentfault.com/a/1190000007376061


以上是詳細解讀js中的繼承機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn