1. 程式人生 > >上帝視角一文理解JavaScript原型和原型鏈

上帝視角一文理解JavaScript原型和原型鏈

本文呆鵝原創,原文地址:[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)