動圖學 JavaScript 之:原型繼承

前言

你是否曾思考爲何咱們能使用 JS 中的一些內置屬性和方法,好比 .length.split().join()?咱們並無顯式地聲明它們,那麼究竟它們從哪裏來的呢? 可不要說什麼「那是 JS 中的魔法!」。其實這些都由於一個叫作 原型繼承(prototypal inheritance) 的東西。它太棒啦,你平時也常常會用到,只不過可能沒有注意!javascript

構造函數

咱們常常須要建立不少相同類型的對象。想象一下咱們有個網站,上面都是狗狗~java

對於每一個狗狗來講,咱們須要一個對象來表示它!爲了避免每次都新建立一個對象,就須要寫一個構造函數(稍後再說 ES6 中的類哈~)。有了構造函數,就能夠用 new 關鍵字來建立狗狗的 實例(instance) 了。es6

每隻狗都有一個 名字(name)品種(breed)顏色(color) 和一個 吠(bark) 方法。segmentfault

1-dog.png

當咱們建立了這個 Dog 構造函數,它並非咱們建立的惟一對象(要知道函數也是對象)。自動地,咱們建立了另外一個 prototype 對象。默認狀況下,這個對象有一個 constructor 屬性,指向的就是咱們的 Dog 構造器。函數

2-prototype.gif

這個在 Dog 構造器上的 prototype 屬性是不可枚舉的,意味着當你嘗試訪問對象屬性時,該屬性不會顯示。可是它仍然在那裏!oop

原型繼承

好吧~那麼爲何須要有該屬性對象呢?首先,讓咱們來建立幾隻狗狗。簡單起見,咱們就叫它們 dog1dog2dog1Daisy,是一隻可愛的拉布拉多(Labrador)!dog2Jack,是一隻勇敢的傑克羅素犬(Jack Russell)~學習

3-dog-instance.png

讓咱們將 dog1 打印到控制檯,而後展開它的屬性:網站

4-dog1-log.gif

能夠看到咱們添加的屬性有:namebreedcolorbark,可是快看,還有一個 __proto__ 屬性!它也是不可枚舉的,因此一般咱們在獲取對象屬性的時候也看不到它。讓咱們展開看看:spa

5-proto.gif

是否是看起來和 Dog.prototype 同樣哈!你猜怎麼着,這個 __proto__ 就是對 Dog.prototype 的引用。這就是 原型繼承 的所有內容:構造函數創造的每一個實例都可以訪問構造函數的原型。prototype

6-instance-to-prototype.gif

原型繼承的好處

那麼爲何這很酷?有時候咱們擁有每一個實例共享的屬性。好比 bark 方法:它在每一個實例中都是相同的,那爲何每次建立新實例都要新建一個 bark 方法,很明顯這樣會浪費內存。相反地,咱們能夠將 bark 方法添加到 Dog.prototype 上去!

7-prototype-bark.gif

這樣每當咱們訪問實例的屬性時,引擎首先檢查該屬性在實例上是否認義,若是沒有找到,就會經過 __proto__ 屬性,順着原型鏈 繼續查找。

8-prototype-chain.gif

不止是一層

這只是一個步驟,其實能夠包含多個步驟!若是繼續進行下去,你可能會注意到,當我展開 Dog.prototype__proto__ 對象時,我沒有包含一個屬性。因爲 Dog.prototype 本身也是一個對象,這意味着它其實是 Object 構造函數的實例。這意味着 Dog.prototype 也有一個 __proto__ 屬性,而且指向了 Object.prototype

9-object-prototype.gif

好比說 .toString() 這個方法。它是定義在 dog1 上麼?明顯不是。那麼在 Dog.prototype 上有麼?也沒有!其實它是定義在 Dog.prototype.__proto__ 上,即 Object.prototype 上。

10-toString.gif

ES6 中的類

前面咱們使用的是構造函數的方式(function Dog() { ... }),實際上 ES6 中提供了構造函數和原型的更簡單的語法:類(Classes)

只是 構造函數語法糖。一切都是以相同的方式工做!

咱們使用 class 關鍵字來定義類。每一個類都有一個 constructor 函數,基本上對應了 ES6 中構造函數的寫法。而咱們想要添加到原型 prototype 上的屬性和方法,均可以在類中直接定義。

11-class.gif

關於類的另外一個好處就是,咱們能夠輕鬆地 擴展(extend) 其餘的類。

類的繼承

假如咱們要添加另外一種狗,吉娃娃(Chihuahuas)狗。爲了便於理解,咱們只添加一個 name 屬性。可是吉娃娃也能夠有本身特殊的叫聲!和普通的叫聲不一樣,吉娃娃的叫聲可能比較小~

在子類中,咱們能夠經過 super 關鍵字訪問到父類的構造方法。參數固然也參考父類的構造方法傳入。

12-extends.png

myPet 能夠訪問到 Chihuahua.prototypeDog.prototype (固然也有 Object.prototype ,由於 Dog.prototype 是個對象)。

13-es6-inheritance.gif

因爲 Chihuahua.prototype 上有一個 smallBark 方法,Dog.prototype 上有一個 bark 方法,因此咱們能夠在 myPet 實例上同時訪問到 smallBarkbark 方法。

原型的終點

如今,你能夠想象,原型鏈不會永遠持續下去。最終會有一個原型等於 null 的對象:它就是 Object.prototype。若是咱們試圖訪問在本地或者原型鏈上都不存在的屬性,最終會返回 undefined

14-proto-end.gif

Object.create

儘管上面已經解釋了構造函數和類,其實還有一個爲對象添加原型的方式是使用 Object.create 方法。經過這個方法,咱們建立了一個新對象,而且指明瞭這個對象的原型是什麼。

只須要將一個已經存在的對象傳入 Object.create 方法中。建立出來的對象就是以咱們傳入的對象做爲原型。看例子:

15-object-create.png

咱們打印一下 me ,能夠看到:

16-me-proto.gif

咱們並無爲 me 對象添加其餘的屬性,可是訪問它卻有一個 __proto__ 屬性,而且這個屬性指向的是具備 nameage 的對象 person。而 person 這個對象的 __proto__ 屬性指向的是 Object.prototype

全文就到這裏啦,但願對你學習原型繼承有幫助~

本文是翻譯的系列文章:

參考連接


本文首發於公衆號:碼力全開(codingonfire)

本文隨意轉載哈,註明原文連接便可,公號文章轉載聯繫我開白名單就好~

codingonfire.jpg

相關文章
相關標籤/搜索