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

上帝視角一文理解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())和ObjectStringNumber等這些物件一樣,都是物件。

既然都是物件,當然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),拉你進技術群,長期交流學習
  • 關注公眾號「呆鵝實驗室」,和呆鵝一起學前端,提高技術認知