上帝視角一文理解JavaScript原型和原型鏈
本文呆鵝原創,原文地址:https://juejin.im/user/307518987058686/posts
前言
本文將從上帝角度
講解JS的世界,在這個過程中,大家就能完全理解JS的原型和原型鏈是什麼,之後還會基於原型和原型鏈知識拓展一些相關知識。
閱讀本文前可以思考下面三個問題:
- 你理解中的原型和原型鏈是什麼?
- 你能完全理解並畫出原型和原型鏈的關係圖嗎?
- 基於原型和原型鏈拓展的相關知識你瞭解多少?
經典圖
大家接觸原型和原型鏈時應該都看到過下面這張圖。剛開始瞭解的時候,看到這個圖大都不太明白,甚至一臉懵,這裡先留個坑。
先祭圖,讓暴風雨來得更猛烈些!
下面開始講解JS世界是如何一步步誕生的,看完你也就完全明白這張神圖啦。
無中生有
起初,上帝JS掌控的世界什麼都沒有。
上帝JS說:沒有東西本身也是一種東西啊,於是就有了null
:
現在我們要造點兒東西出來。但是沒有原料怎麼辦?
有一個聲音說:現在不是有null
了嘛?
上帝說:那就無中生有吧!
JavaScript中的1號物件產生了,不妨把它叫做機器1號。
這個機器1號可不得了,它是JS世界的第一個物件,它是真正的萬物始祖。它擁有的性質,是所有的物件都有的。
__proto__
是什麼呢?是“生”的意思,或者換成專業點的叫法“繼承”。
有中生有
剛開始造物,上帝當然想繼續下去啦,既然已經有了一個始祖級的機器,剩下就好辦了,因為一生二,二生三,三生萬物嘛。
不過上帝很懶,他不想一個一個地親手製造物件。於是他做了一臺能夠製造新物件的東西:
他給這個東西起了一個名字:Object。
但是這個Object製造物件時候,需要有一個模版,現在只有機器1號,它就取了機器1號當模版。圖中的prototype
就代表模板物件。
如何啟動製造呢?通過new
命令。你按下“new”按鈕,新的物件就造出來了。
把這個過程寫成程式碼就是:
var obj = new Object();
轟轟烈烈的造物運動開始了……
有生萬物
有一天,上帝JS去看了上帝Java造的世界,發現上帝Java的世界好精彩,可不僅僅有Object物件,還有String物件、Number物件、Boolean物件等等。
於是上帝就思考了:那我可以多讓機器造一些物件啊。
但是上帝覺得把這些工作都交給機器1號的話,機器1號太累了,不如讓機器1號造一個機器2號來做這些工作。
重點說明下“這些工作”指的是:總體負責製造所有的物件,包含Object、String、Number、Boolean、Array,甚至還有之後的Function。當然它只是負責製造,並不一定會親手去製造物件,可以通過製造對應的機器來幫助它製造物件
於是就有了機器2號:
(注:__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等都是同一個道理的):
這樣,這張圖顯示了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的一個非常特別的性質:
Function.__proto__ === Function.prototype
於是JavaScript的世界的變成了下面的樣子:
到現在我們能明白啦:
- 機器1號 = Object.prototype
- 機器2號 = Function.prototype
- 機器String號 = String.prototype
世界動起來
自從有了Function,世界就越來越有活力了,有什麼事需要做,用new Function()造個新Function來做就行了。
但是剛造出來的Function機器很難用,用法就像下面這個:
let Foo = new Function("name", "console.log(name)");
Foo('dellyoung'); // 控制檯打印出:dellyoung
你想要造一個Function,無論是輸入的內容(引數)還是要做的事情(函式體)都得弄成字串,才能成功造出來。
上帝用起來難受啊,他就改裝了一下這個Function,給他來了個語法糖
function Foo(name) {
console.log(name);
}
Foo('dellyoung'); // 控制檯打印出:dellyoung
(注:上面兩段程式碼是完全等價的。)
現在造一個新的Function就舒服多啦!
以造Foo()
為例,於是JavaScript的世界的變成了下面的樣子:
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()號
。
聽起來好像有點繞,咱們看看圖就明白啦
上圖中:
- 機器1號 =
Object.prototype
- 機器2號 =
Function.prototype
- 機器String號 =
String.prototype
- 機器Foo()號 =
Foo.prototype
- [p] =
__proto__
回到現實
現在我們就能完全理解並完整的畫出原型和原型鏈的關係圖啦:
其實可以被用來new
的物件或函式,我們都可以稱之為建構函式,每個建構函式都和它的機器(也就是XXX.prototype
)通過constructor
相連,我們來畫出建構函式和它們的constructor
:
為了清晰一些,上圖用
[con]
表示constructor
現在這張圖就是完整的原型和原型鏈的關係圖啦
用正式的語言總結一下就是:
-
子類的__proto__屬性,表示建構函式的繼承,總是指向父類。
-
子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。
現在我們再看這張神圖:
是不是很簡單啦,不過就是把咱們畫的圖向右旋轉了90°。
而且仔細看一遍,咱們的關係圖包含的更加的全面。
填坑完畢。下篇文章我會以此為延伸,從底層講解JavaScript的this,看完你會徹底理解this為何物,關注我
看完兩件事
- 歡迎加我微信(iamyyymmm),拉你進技術群,長期交流學習
- 關注公眾號「呆鵝實驗室」,和呆鵝一起學前端,提高技術認知