天天看點

動圖學 JavaScript 之:原型繼承

前言

你是否曾思考為什麼我們能使用 JS 中的一些内置屬性和方法,比如

.length

.split()

.join()

?我們并沒有顯式地聲明它們,那麼究竟它們從哪裡來的呢?可不要說什麼“那是 JS 中的魔法!”。其實這些都因為一個叫做 原型繼承(prototypal inheritance) 的東西。它太棒啦,你平時也經常會用到,隻不過可能沒有注意!

構造函數

我們經常需要建立很多相同類型的對象。想象一下我們有個網站,上面都是狗狗~

對于每個狗狗來說,我們需要一個對象來表示它!為了不每次都新建立一個對象,就需要寫一個構造函數(稍後再說 ES6 中的類哈~)。有了構造函數,就可以用

new

關鍵字來建立狗狗的 執行個體(instance) 了。

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

動圖學 JavaScript 之:原型繼承

當我們建立了這個

Dog

構造函數,它并不是我們建立的唯一對象(要知道函數也是對象)。自動地,我們建立了另一個

prototype

對象。預設情況下,這個對象有一個

constructor

屬性,指向的就是我們的

Dog

構造器。

動圖學 JavaScript 之:原型繼承

這個在

Dog

構造器上的

prototype

屬性是不可枚舉的,意味着當你嘗試通路對象屬性時,該屬性不會顯示。但是它仍然在那裡!

原型繼承

好吧~那麼為什麼需要有該屬性對象呢?首先,讓我們來建立幾隻狗狗。簡單起見,我們就叫它們

dog1

dog2

dog1

Daisy

,是一隻可愛的拉布拉多(Labrador)!

dog2

Jack

,是一隻勇敢的傑克羅素犬(Jack Russell)~

動圖學 JavaScript 之:原型繼承

讓我們将

dog1

列印到控制台,然後展開它的屬性:

動圖學 JavaScript 之:原型繼承

可以看到我們添加的屬性有:

name

breed

color

bark

,但是快看,還有一個

__proto__

屬性!它也是不可枚舉的,是以通常我們在擷取對象屬性的時候也看不到它。讓我們展開看看:

動圖學 JavaScript 之:原型繼承

是不是看起來和

Dog.prototype

一樣哈!你猜怎麼着,這個

__proto__

就是對

Dog.prototype

的引用。這就是 原型繼承 的全部内容:構造函數創造的每個執行個體都能夠通路構造函數的原型。

動圖學 JavaScript 之:原型繼承

原型繼承的好處

那麼為什麼這很酷?有時候我們擁有每個執行個體共享的屬性。比如

bark

方法:它在每個執行個體中都是相同的,那為什麼每次建立新執行個體都要建立一個

bark

方法,很明顯這樣會浪費記憶體。相反地,我們可以将

bark

方法添加到

Dog.prototype

上去!

動圖學 JavaScript 之:原型繼承

這樣每當我們通路執行個體的屬性時,引擎首先檢查該屬性在執行個體上是否定義,如果沒有找到,就會通過

__proto__

屬性,順着原型鍊 繼續查找。

動圖學 JavaScript 之:原型繼承

不止是一層

這隻是一個步驟,其實可以包含多個步驟!如果繼續進行下去,你可能會注意到,當我展開

Dog.prototype

__proto__

對象時,我沒有包含一個屬性。由于

Dog.prototype

自己也是一個對象,這意味着它實際上是

Object

構造函數的執行個體。這意味着

Dog.prototype

也有一個

__proto__

屬性,并且指向了

Object.prototype

動圖學 JavaScript 之:原型繼承

比如說

.toString()

這個方法。它是定義在

dog1

上麼?明顯不是。那麼在

Dog.prototype

上有麼?也沒有!其實它是定義在

Dog.prototype.__proto__

上,即

Object.prototype

上。

動圖學 JavaScript 之:原型繼承

ES6 中的類

前面我們使用的是構造函數的方式(

function Dog() { ... }

),實際上 ES6 中提供了構造函數和原型的更簡單的文法:類(Classes)

類 隻是 構造函數 的 文法糖。一切都是以相同的方式工作!

我們使用

class

關鍵字來定義類。每個類都有一個

constructor

函數,基本上對應了 ES6 中構造函數的寫法。而我們想要添加到原型

prototype

上的屬性和方法,都可以在類中直接定義。

動圖學 JavaScript 之:原型繼承

關于類的另一個好處就是,我們可以輕松地 擴充(extend) 其他的類。

類的繼承

假如我們要添加另一種狗,吉娃娃(Chihuahuas)狗。為了便于了解,我們隻添加一個

name

屬性。但是吉娃娃也可以有自己特殊的叫聲!和普通的叫聲不同,吉娃娃的叫聲可能比較小~

在子類中,我們可以通過

super

關鍵字通路到父類的構造方法。參數當然也參考父類的構造方法傳入。

動圖學 JavaScript 之:原型繼承

myPet

可以通路到

Chihuahua.prototype

Dog.prototype

(當然也有

Object.prototype

,因為

Dog.prototype

是個對象)。

動圖學 JavaScript 之:原型繼承

由于

Chihuahua.prototype

上有一個

smallBark

方法,

Dog.prototype

上有一個

bark

方法,是以我們可以在

myPet

執行個體上同時通路到

smallBark

bark

方法。

原型的終點

現在,你可以想象,原型鍊不會永遠持續下去。最終會有一個原型等于

null

的對象:它就是

Object.prototype

。如果我們試圖通路在本地或者原型鍊上都不存在的屬性,最終會傳回

undefined

動圖學 JavaScript 之:原型繼承

Object.create

盡管上面已經解釋了構造函數和類,其實還有一個為對象添加原型的方式是使用

Object.create

方法。通過這個方法,我們建立了一個新對象,并且指明了這個對象的原型是什麼。

隻需要将一個已經存在的對象傳入

Object.create

方法中。建立出來的對象就是以我們傳入的對象作為原型。看例子:

動圖學 JavaScript 之:原型繼承

我們列印一下

me

,可以看到:

動圖學 JavaScript 之:原型繼承

我們并沒有為

me

對象添加其他的屬性,但是通路它卻有一個

__proto__

屬性,并且這個屬性指向的是具有

name

age

的對象

person

。而

person

這個對象的

__proto__

屬性指向的是

Object.prototype

全文就到這裡啦,希望對你學習原型繼承有幫助~

參考連結

  • JavaScript Visualized: Prototypal Inheritance

本文首發于公衆号:碼力全開(codingonfire)

本文随意轉載哈,注明原文連結即可