你是否曾思考爲何咱們能使用 JS 中的一些內置屬性和方法,好比 .length
,.split()
,.join()
?咱們並無顯式地聲明它們,那麼究竟它們從哪裏來的呢? 可不要說什麼「那是 JS 中的魔法!」。其實這些都由於一個叫作 原型繼承(prototypal inheritance) 的東西。它太棒啦,你平時也常常會用到,只不過可能沒有注意!javascript
咱們常常須要建立不少相同類型的對象。想象一下咱們有個網站,上面都是狗狗~java
對於每一個狗狗來講,咱們須要一個對象來表示它!爲了避免每次都新建立一個對象,就須要寫一個構造函數(稍後再說 ES6 中的類哈~)。有了構造函數,就能夠用 new
關鍵字來建立狗狗的 實例(instance) 了。es6
每隻狗都有一個 名字(name),品種(breed),顏色(color) 和一個 吠(bark) 方法。segmentfault
當咱們建立了這個 Dog
構造函數,它並非咱們建立的惟一對象(要知道函數也是對象)。自動地,咱們建立了另外一個 prototype
對象。默認狀況下,這個對象有一個 constructor
屬性,指向的就是咱們的 Dog
構造器。函數
這個在 Dog
構造器上的 prototype
屬性是不可枚舉的,意味着當你嘗試訪問對象屬性時,該屬性不會顯示。可是它仍然在那裏!oop
好吧~那麼爲何須要有該屬性對象呢?首先,讓咱們來建立幾隻狗狗。簡單起見,咱們就叫它們 dog1
和 dog2
。dog1
叫 Daisy
,是一隻可愛的拉布拉多(Labrador)!dog2
叫 Jack
,是一隻勇敢的傑克羅素犬(Jack Russell)~學習
讓咱們將 dog1
打印到控制檯,而後展開它的屬性:網站
能夠看到咱們添加的屬性有:name
,breed
,color
和 bark
,可是快看,還有一個 __proto__
屬性!它也是不可枚舉的,因此一般咱們在獲取對象屬性的時候也看不到它。讓咱們展開看看:spa
是否是看起來和 Dog.prototype
同樣哈!你猜怎麼着,這個 __proto__
就是對 Dog.prototype
的引用。這就是 原型繼承 的所有內容:構造函數創造的每一個實例都可以訪問構造函數的原型。prototype
那麼爲何這很酷?有時候咱們擁有每一個實例共享的屬性。好比 bark
方法:它在每一個實例中都是相同的,那爲何每次建立新實例都要新建一個 bark
方法,很明顯這樣會浪費內存。相反地,咱們能夠將 bark
方法添加到 Dog.prototype
上去!
這樣每當咱們訪問實例的屬性時,引擎首先檢查該屬性在實例上是否認義,若是沒有找到,就會經過 __proto__
屬性,順着原型鏈 繼續查找。
這只是一個步驟,其實能夠包含多個步驟!若是繼續進行下去,你可能會注意到,當我展開 Dog.prototype
的 __proto__
對象時,我沒有包含一個屬性。因爲 Dog.prototype
本身也是一個對象,這意味着它其實是 Object
構造函數的實例。這意味着 Dog.prototype
也有一個 __proto__
屬性,而且指向了 Object.prototype
。
好比說 .toString()
這個方法。它是定義在 dog1
上麼?明顯不是。那麼在 Dog.prototype
上有麼?也沒有!其實它是定義在 Dog.prototype.__proto__
上,即 Object.prototype
上。
前面咱們使用的是構造函數的方式(function Dog() { ... }
),實際上 ES6 中提供了構造函數和原型的更簡單的語法:類(Classes)
類 只是 構造函數 的 語法糖。一切都是以相同的方式工做!
咱們使用 class
關鍵字來定義類。每一個類都有一個 constructor
函數,基本上對應了 ES6 中構造函數的寫法。而咱們想要添加到原型 prototype
上的屬性和方法,均可以在類中直接定義。
關於類的另外一個好處就是,咱們能夠輕鬆地 擴展(extend) 其餘的類。
假如咱們要添加另外一種狗,吉娃娃(Chihuahuas)狗。爲了便於理解,咱們只添加一個 name
屬性。可是吉娃娃也能夠有本身特殊的叫聲!和普通的叫聲不一樣,吉娃娃的叫聲可能比較小~
在子類中,咱們能夠經過 super
關鍵字訪問到父類的構造方法。參數固然也參考父類的構造方法傳入。
myPet
能夠訪問到 Chihuahua.prototype
和 Dog.prototype
(固然也有 Object.prototype
,由於 Dog.prototype
是個對象)。
因爲 Chihuahua.prototype
上有一個 smallBark
方法,Dog.prototype
上有一個 bark
方法,因此咱們能夠在 myPet
實例上同時訪問到 smallBark
和 bark
方法。
如今,你能夠想象,原型鏈不會永遠持續下去。最終會有一個原型等於 null
的對象:它就是 Object.prototype
。若是咱們試圖訪問在本地或者原型鏈上都不存在的屬性,最終會返回 undefined
。
儘管上面已經解釋了構造函數和類,其實還有一個爲對象添加原型的方式是使用 Object.create
方法。經過這個方法,咱們建立了一個新對象,而且指明瞭這個對象的原型是什麼。
只須要將一個已經存在的對象傳入 Object.create
方法中。建立出來的對象就是以咱們傳入的對象做爲原型。看例子:
咱們打印一下 me
,能夠看到:
咱們並無爲 me
對象添加其餘的屬性,可是訪問它卻有一個 __proto__
屬性,而且這個屬性指向的是具備 name
和 age
的對象 person
。而 person
這個對象的 __proto__
屬性指向的是 Object.prototype
。
全文就到這裏啦,但願對你學習原型繼承有幫助~
本文是翻譯的系列文章:
本文首發於公衆號:碼力全開(codingonfire)
本文隨意轉載哈,註明原文連接便可,公號文章轉載聯繫我開白名單就好~