最近在學vue,到週末終於有空寫一些東西了(想想又能騙贊,就有點小激動!)。在javascript基礎中,除了閉包之外,繼承也是一個困難。因為考慮到篇幅較長,所以打算分成兩個部分來寫。同樣基於《javascript高級程式設計》,做一個詳細的講解,如果有不對的地方歡迎指正。
為了更好的講解繼承,先把一些準備知識放在前面。
建構函數,是用來建立物件的函數,本質上也是函數。與其他函數的差別在於呼叫方式不同:
如果透過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(){省略内容...}
當我們每次建立一個函數的時候,函數物件都會有一個prototype
屬性,這個屬性是一個指標,指向它的原型物件。 原型物件的本質也是一個物件。初次看這句話可能有點難以理解,舉個例子,還是剛剛那個函數:
function Person(name, age) { this.name = name; this.age = age; } console.log(Person.prototype)//object{constructor:Person}
可以看到Person.prototype
指向了一個對象,即Person的原型物件,而這個物件有一個constructor
屬性,又指向了Person
函數物件。是不是有點暈?沒關係,接下來我們就上比舉例子更好的手段--畫圖。
在前面,我們剛剛介紹過了建構函數,實例和原型對象,接下來我們用一張圖來表示這三者之間的關係(用ps畫這種圖真是麻煩的要死,大家有好的工具推薦一下):
#從圖上我們可以看到:
函數物件的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'為了方便理解剛剛發生了什麼,我們再上一張圖:
##現在結合圖片來看程式碼:
接著,我們為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沿著[Prototype]屬性可以存取B prototype,B prototype繼續沿著
[Prototype]屬性存取A prototype,最後在A.prototype上找到了sayA()方法,所以可以執行
b1繼承了A的屬性和方法,這種由[Prototype]不斷把實例和原型物件連結起來的結構就是原型鏈
。也是js中,繼承主要的實作方式。
以上是詳細解讀js中的繼承機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!