上帝視角一文理解JavaScript原型和原型鏈
阿新 • • 發佈:2020-10-10
本文呆鵝原創,原文地址:[https://juejin.im/user/307518987058686/posts](https://juejin.im/user/307518987058686/posts)
## 前言
本文將從`上帝角度`講解JS的世界,在這個過程中,大家就能完全理解JS的原型和原型鏈是什麼,之後還會基於原型和原型鏈知識拓展一些相關知識。
閱讀本文前可以思考下面三個問題:
- 你理解中的原型和原型鏈是什麼?
- 你能完全理解並畫出原型和原型鏈的關係圖嗎?
- 基於原型和原型鏈拓展的相關知識你瞭解多少?
## 經典圖
大家接觸原型和原型鏈時應該都看到過下面這張圖。剛開始瞭解的時候,看到這個圖大都不太明白,甚至一臉懵,這裡先留個坑。
![](https://user-gold-cdn.xitu.io/2020/2/25/1707b9b1d76d9f07?w=486&h=586&f=jpeg&s=55985)
> 先祭圖,讓暴風雨來得更猛烈些!
下面開始講解JS世界是如何一步步誕生的,看完你也就完全明白這張神圖啦。
## 無中生有
起初,上帝JS掌控的世界什麼都沒有。
上帝JS說:沒有東西本身也是一種東西啊,於是就有了`null`:
![](https://user-gold-cdn.xitu.io/2020/2/25/1707bb65ef45ef44?w=1576&h=612&f=jpeg&s=90655)
現在我們要造點兒東西出來。但是沒有原料怎麼辦?
有一個聲音說:現在不是有`null`了嘛?
上帝說:那就無中生有吧!
![](https://user-gold-cdn.xitu.io/2020/2/25/1707bbe0dc570f17?w=1570&h=600&f=png&s=190692)
JavaScript中的1號物件產生了,不妨把它叫做機器1號。
這個機器1號可不得了,它是JS世界的第一個物件,它是真正的萬物始祖。它擁有的性質,是所有的物件都有的。
`__proto__`是什麼呢?是“生”的意思,或者換成專業點的叫法“繼承”。
## 有中生有
剛開始造物,上帝當然想繼續下去啦,既然已經有了一個始祖級的機器,剩下就好辦了,因為一生二,二生三,三生萬物嘛。
不過上帝很懶,他不想一個一個地親手製造物件。於是他做了一臺能夠製造新物件的東西:
![](https://user-gold-cdn.xitu.io/2020/2/25/1707bc7c23cddfab?w=1560&h=616&f=png&s=214003)
他給這個東西起了一個名字:Object。
但是這個Object製造物件時候,需要有一個模版,現在只有機器1號,它就取了機器1號當模版。圖中的`prototype`就代表模板物件。
如何啟動製造呢?通過`new`命令。你按下“new”按鈕,新的物件就造出來了。
![](https://user-gold-cdn.xitu.io/2020/2/25/1707bd67c976d9fb?w=1562&h=598&f=png&s=252007)
把這個過程寫成程式碼就是:
```javascript
var obj = new Object();
```
轟轟烈烈的造物運動開始了……
## 有生萬物
有一天,上帝JS去看了上帝Java造的世界,發現上帝Java的世界好精彩,可不僅僅有Object物件,還有String物件、Number物件、Boolean物件等等。
於是上帝就思考了:那我可以多讓機器造一些物件啊。
但是上帝覺得把這些工作都交給機器1號的話,機器1號太累了,不如讓機器1號造一個機器2號來做這些工作。
**重點說明下“這些工作”指的是:總體負責製造所有的物件,包含Object、String、Number、Boolean、Array,甚至還有之後的Function。當然它只是負責製造,並不一定會親手去製造物件,可以通過製造對應的機器來幫助它製造物件**
於是就有了機器2號:
![](https://user-gold-cdn.xitu.io/2020/2/25/1707be745dbe149a?w=1564&h=1206&f=png&s=547695)
(注:`__proto__`寫起來麻煩,我們之後用`[p]`來代替)
可能有的小夥伴注意到啦,`Object`也指向了機器2號,這是因為機器2號是負責造物件的,當然也負責造`Object`物件啦。
接下來,既然機器2號是由機器1號造出來的,而`String、Number、Boolean、Array`這些物件是由機器2號造出來的,所以它們其實和`Object`一樣,也自帶了`new`命令:你按下“new”按鈕,新的物件就造出來了。
但是Object有自己的模板:機器1號。而`String、Number、Boolean、Array`它們有模板嗎?
其實機器2號在建立它們的時候並不是直接建立它們的,而是先建立了對應物件的機器作為模板,然後再由各自的機器來建立它們。
具體我畫了String相關的圖(其他Number、Boolean、Array等都是同一個道理的):
![](https://user-gold-cdn.xitu.io/2020/2/25/1707bfdc95bd7c30?w=1802&h=1206&f=png&s=668183)
這樣,這張圖顯示了JS世界中那些最基本的機器本身的原型鏈,以及它們的模板物件的原型鏈。
- 機器1號製造了機器2號,機器2號總體負責各種物件的製造
- 但是機器2號並不是直接造各種物件,而是通過先建造對應的機器,再由對應的機器來製造物件。如:對於String,`機器2號`先製造了`機器String號`,然後由`機器String號`來製造String,`機器2號`只負責總體控制
- 雖然機器2號製造了各種各樣的機器,但是因為機器2號是由機器1號製造的,所以這些被製造的機器所屬權還是歸於機器1號的,畢竟機器1號是始祖級的。
- 物件和對應的機器,通過`prototype`來連線。如:對於String,`機器2號`和`String`通過`prototype`連線
- 每個機器都有且只有一個的物件,每個物件也都有且只有一個機器作為模板。
## 萬物缺活力
上帝看著越來越豐富的世界非常高興,但是總感覺缺點什麼?
一個聲音說:世界缺少活力呀
上帝說:那就造一個**能讓世界動起來的物件**
上帝給這個新物件的起了個名字叫:Funciton
但是這個製造Function的工作交給誰好呢,讓世界動起來當然是非常重要的,那就交給機器2號吧,**由機器2號親手負責Function的製造**
於是,Function物件就出現了
讓我們來觀察一下Function物件:
- 它是由機器2號親手製造的,所以它們之間有`prototype`相連
- 而機器2號又是製造所有物件的負責者,所以它們之間有`__proto__`相連
於是我們得到了Function的一個非常特別的性質:
```javascript
Function.__proto__ === Function.prototype
```
於是JavaScript的世界的變成了下面的樣子:
![](https://user-gold-cdn.xitu.io/2020/2/26/1707f31cdb253b64?w=1802&h=1200&f=png&s=690113)
到現在我們能明白啦:
- 機器1號 = Object.prototype
- 機器2號 = Function.prototype
- 機器String號 = String.prototype
## 世界動起來
自從有了Function,世界就越來越有活力了,有什麼事需要做,用new Function()造個新Function來做就行了。
但是剛造出來的Function機器**很難用**,用法就像下面這個:
```javascript
let Foo = new Function("name", "console.log(name)");
Foo('dellyoung'); // 控制檯打印出:dellyoung
```
你想要造一個Function,無論是輸入的內容(引數)還是要做的事情(函式體)都得弄成字串,才能成功造出來。
上帝用起來難受啊,他就改裝了一下這個Function,給他來了個語法糖
```javascript
function Foo(name) {
console.log(name);
}
Foo('dellyoung'); // 控制檯打印出:dellyoung
```
(注:上面兩段程式碼是完全等價的。)
現在造一個新的Function就舒服多啦!
以造`Foo()`為例,於是JavaScript的世界的變成了下面的樣子:
![](https://user-gold-cdn.xitu.io/2020/2/26/1707f53b285b7b85?w=1804&h=1198&f=png&s=718972)
Function這個物件比較特殊,它new出來後,就是一個全新的物件了,`function Foo()`(注意:它等價於 `let Foo = new Function()`)和`Object`、`String`、`Number`等這些物件一樣,都是物件。
既然都是物件,當然`function Foo()`也是由機器2號來控制製造的,但是機器2號很忙,它沒有精力直接製造`function Foo()`,機器2號是通過製造出一個`製造function Foo()的機器`來製造`function Foo()`。
咱們稱`製造function Foo()的機器`為`機器Foo()號`
當然既然是機器,所以`機器Foo()號`也是`由機器1號`控制的,原因上文講過:
> 雖然機器2號製造了各種各樣的機器,但是因為機器2號是由機器1號製造的,所以這些被製造的機器所屬權還是歸於機器1號的,畢竟機器1號是始祖級的。
而且這個`function Foo()`物件製造出來後,它既然是物件,所以它和Object、String、Number等物件一樣,可以通過`new Foo()`製造出新的物件,模板就是用的`機器Foo()號`。
聽起來好像有點繞,咱們看看圖就明白啦
![](https://user-gold-cdn.xitu.io/2020/2/26/17081e6d99a73009?w=2020&h=1198&f=png&s=817914)
上圖中:
- 機器1號 = `Object.prototype`
- 機器2號 = `Function.prototype`
- 機器String號 = `String.prototype`
- 機器Foo()號 = `Foo.prototype`
- [p] = `__proto__`
## 回到現實
現在我們就能完全理解並完整的畫出原型和原型鏈的關係圖啦:
![](https://user-gold-cdn.xitu.io/2020/2/26/17081e51c293b58a?w=2016&h=1198&f=png&s=815702)
其實可以被用來`new`的物件或函式,我們都可以稱之為建構函式,每個建構函式都和它的機器(也就是`XXX.prototype`)通過`constructor`相連,我們來畫出建構函式和它們的`constructor`:
![](https://user-gold-cdn.xitu.io/2020/2/26/17081f5c5ab75395?w=2018&h=1198&f=png&s=889454)
> 為了清晰一些,上圖用`[con]`表示`constructor`
現在這張圖就是完整的原型和原型鏈的關係圖啦
用正式的語言總結一下就是:
- 子類的__proto__屬性,表示建構函式的繼承,總是指向父類。
- 子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。
**現在我們再看這張神圖:** ![](https://user-gold-cdn.xitu.io/2020/2/25/1707b9b1d76d9f07?w=486&h=586&f=jpeg&s=55985) 是不是很簡單啦,不過就是把咱們畫的圖向右旋轉了90°。 而且仔細看一遍,咱們的關係圖包含的更加的全面。 > 填坑完畢。下篇文章我會以此為延伸,從底層講解JavaScript的this,看完你會徹底理解this為何物,關注我 ## 看完兩件事 - 歡迎加我微信(iamyyymmm),拉你進技術群,長期交流學習 - 關注公眾號「呆鵝實驗室」,和呆鵝一起學前端,提高技術認知 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gjdcbqfm0rj30wa0gu74y.jpg)
**現在我們再看這張神圖:** ![](https://user-gold-cdn.xitu.io/2020/2/25/1707b9b1d76d9f07?w=486&h=586&f=jpeg&s=55985) 是不是很簡單啦,不過就是把咱們畫的圖向右旋轉了90°。 而且仔細看一遍,咱們的關係圖包含的更加的全面。 > 填坑完畢。下篇文章我會以此為延伸,從底層講解JavaScript的this,看完你會徹底理解this為何物,關注我 ## 看完兩件事 - 歡迎加我微信(iamyyymmm),拉你進技術群,長期交流學習 - 關注公眾號「呆鵝實驗室」,和呆鵝一起學前端,提高技術認知 ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gjdcbqfm0rj30wa0gu74y.jpg)