最近在學vue,到週末終於有空寫一些東西了(想一想又能騙贊,就有點小激動!)。在javascript基礎中,除了閉包以外,繼承也是一個難點。由於考慮到篇幅較長,因此打算分紅兩個部分來寫。一樣基於《javascript高級程序設計》,作一個詳細的講解,若是有不對的地方歡迎指正。javascript
爲了更好的講解繼承,先把一些準備知識放在前面。vue
構造函數,是用來建立對象的函數,本質上也是函數。與其餘函數的區別在於調用方式不一樣: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(){省略內容...}
當咱們每次建立一個函數的時候,函數對象都會有一個prototype
屬性,這個屬性是一個指針,指向它的原型對象。原型對象的本質也是一個對象。初次看這句話可能有點難以理解,舉個例子,仍是剛剛那個函數:工具
function Person(name, age) { this.name = name; this.age = age; } console.log(Person.prototype)//object{constructor:Person}
能夠看到Person.prototype
指向了一個對象,即Person的原型對象,而且這個對象有一個constructor
屬性,又指向了Person
函數對象。是否是有點暈?不要緊,接下來咱們就上比舉例子更好的手段--畫圖。測試
在前面,咱們剛剛介紹過了構造函數,實例和原型對象,接下來咱們用一張圖來表示這三者之間的關係(用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中,繼承主要的實現方式。
由於這部分知識理解起來比較難,因此第一部分先寫到這裏(固然不是由於我想多寫一篇來騙贊和關注啦),你們讀到這裏也能夠歇口氣了,若是這一塊理解深入,下一部分就會很輕鬆。
爲了測試一下你們對於本文的理解程度,問一下幾個問題:
在最後一個例子裏,console.log(b1.constructor)
,結果是什麼?
B.prototype = new A();
和 B.prototype.sayB = function(){ console.log("from B") }
這兩句的執行順序能不能交換
最後再思考一下. 在最後一個例子裏,A
看似已是原型鏈的最頂層,那A
還能再往上嗎?
以上答案在下篇中解答,讀者能夠本身先試試,思考一下,有疑問也能夠在評論中提出。最後,若是這篇文章對你有幫助,請大方的點收藏和推薦吧(每次都是收藏比推薦多!,組織語言,畫圖和排版都很辛苦的),大家的支持會給我更大的動力~以上內容屬於我的看法,若是有不一樣意見,歡迎指出和探討。請尊重做者的版權,轉載請註明出處,如做商用,請與做者聯繫,感謝!