聲明
本系列文章內容全部梳理自以下幾個來源:
- 《JavaScript權威指南》
- MDN web docs
- Github:smyhvae/web
- Github:goddyZhao/Translation/JavaScript
作為一個前端小白,入門跟着這幾個來源學習,感謝作者的分享,在其基礎上,通過自己的理解,梳理出的知識點,或許有遺漏,或許有些理解是錯誤的,如有發現,歡迎指點下。
PS:梳理的內容以《JavaScript權威指南》這本書中的內容為主,因此接下去跟 JavaScript 語法相關的系列文章基本只介紹 ES5 標准規范的內容、ES6 等這系列梳理完再單獨來講講。
正文-原型鏈
原型鏈也就是對象的繼承結構,舉個例子:
var a = []
那么 a 對象的原型鏈:
a -> Array.prototype -> Object.prototype -> null
基本所有對象的原型鏈頂部都是 Object.prototype,而 Object.prototype 沒有原型,手動通過 Object.create(null) 創建的對象也沒有原型。但這兩點是特例。
原型的用途在於讓對象可繼承原型上的屬性,達到功能復用、代碼復用的目的。
面向對象的編程語言中,繼承是一大特性,所以在編寫 JavaScript 代碼時,要能夠很明確所創建的對象的一個原型鏈結構,這樣才便於更好的設計,更好的編寫代碼。
在編寫代碼過程中,使用的無非就是內置對象,或者自定義對象,所以下面來看看兩者的原型鏈結構:
內置對象的原型鏈結構
其實也就是之前有講過的默認的原型鏈結構:
-
聲明的每個函數 -> Function.prototype –> Object.prototype -> null
-
數組對象 -> Array.prototype -> Object.prototype -> null
-
對象直接量創建的對象 -> Object.prototype -> null
-
日期對象 -> Date.prototype -> Object.prototype -> null
-
正則對象 -> RegExp.prototype -> Object.prototype -> null
可以用對象的 _proto_.constructor.name 來測試:

Object.prototype 已經內置定義了一些屬性,如:toString(),isPrototypeOf(),hasOwnProperty() 等等;
同樣,Array.prototype 內置了如:forEach(),map() 等等。
其他內置原型也都有相對應的一些屬性。
所以使用內置對象時,才可以直接使用內置提供的一些屬性。
自定義對象的原型鏈結構
不手動修改自定義構造函數的 prototype 屬性的話,默認創建的對象的原型鏈結構:
- 自定義構造函數創建的對象 -> {} -> Object.prototype -> null
比如:
function A() {}
var a = new A();
在首次使用構造函數 A 時,內部會去對 prototype 屬性賦值,所進行的工作類似於:A.prototype = new Object();
所以 A.prototype 會指向一個空對象,但這個空對象繼承了 Object.prototype。
那么不修改這條原型鏈的話,默認通過自定義構造函數創建的對象的繼承結構也就是:{} –> Object.prototype –> null。
雖然這條原型鏈也可以這么表示:A.prototype –> Object.prototype -> null
a 雖然確實繼承自 A.prototype,但我不傾向於這種寫法來表示,因為自定義構造函數的 prototype 屬性值會有很大的可能性被修改掉,當它的屬性值重新指向另一個對象后,此時也仍舊可以說 a 對象繼承自 A.prototype,個人感覺理解上會有點別扭,無法區別前后原型的不同,畢竟 A.prototype 只是一個 key 值,所以我傾向於直接說 a 繼承的實際對象,也就是 key 值對應的 value 值。
雖然 Object.prototype 也是一個 key 值,實際指向的一個內置的對象,但手動修改這些內置構造函數的 prototype 的可能性不高,所以個人覺得對於內置構造函數,可以直接用類似 Object.prototype 來表示。
那么這個時候,如果為這個構造函數的 prototype 添加一些屬性:
function A() {}
A.prototype.num = 0;
var a = new A();
那么,對於對象 a 而言,它的原型鏈:
a -> {num:0} -> Object.prototype -> null
這是不修改原型鏈的場景,那么如果手動破壞了默認的原型鏈呢?
var B = [];
B.num = 0;
function A() {}
A.prototype.num = 222;
var a = new A(); //a 的原型鏈
A.prototype = B;
var b = new A(); //b 的原型鏈
此時對象 b 的原型鏈又是什么呢?
首先看看對象 B,是一個數組對象,所以 B 對象的原型鏈:
B –> Array.prototype -> Object.prototype -> null
再來看看對象 a,創建它時,還並沒有修改構造函數的 prototype,所以它的原型鏈:
a -> {num:222} -> Object.prototype -> null
那么這個時候,手動修改掉了構造函數的 prototype 指向,這之后再通過構造函數 A 創建的對象的原型鏈也就會跟隨着變化,所以對象 b 的原型鏈:
b -> B –> Array.prototype -> Object.prototype -> null
所以,修改構造函數的 prototype,其實相當於將另外一條原型鏈拿來替換掉原本的原型鏈。
原型鏈用途
對於對象,它的本質其實也就是一堆屬性的集合,所以對象的用途是用來操作對象內的屬性的,而當操作對象的屬性時,會有一種類似於作用域鏈機制來尋找屬性。
操作無非分兩種場景,一是讀取對象屬性,二是寫對象屬性,兩種所涉及的處理不一樣。
當讀取對象屬性時,是依靠對象的原型鏈來輔助工作,如果對象內部含有該屬性,則直接讀取,否則沿着原型鏈去尋找這個屬性。
也就是說,對象繼承原型的機制,並不是說,將原型的所有屬性拷貝一份到對象內部,而只是簡單對對象建立一條原型鏈而已。這條原型鏈中保存着各個原型對象的引用,當讀取繼承的屬性時,就可以根據這條原型鏈上的引用訪問到其他原型對象內的屬性了。
因為讀取繼承屬性,本質上是讀取其他對象的屬性,那么,這些原型屬性發生變化時,也才會影響到繼承他們的子對象。
那么,對於寫對象屬性的操作:
這點就由對象的特性決定了:當對一個對象的屬性進行賦值操作時,如果對象內沒有該屬性,那么會動態為該對象添加一個屬性,如果對象內部有該屬性,那么修改屬性值。
對象的屬性寫操作會影響到后續的讀操作,因為如果是讀取對象的某個繼承屬性,本來對象內部沒有該屬性,所以是去讀取的原型內的屬性值。但經過寫操作后,對象內部創建了同名的內部屬性,之后再讀取時,發現內部已經有了,自然不會再去原型鏈中讀取。
獲取對象的原型鏈
掌握了原型鏈的相關理論,對於代碼中某個對象的原型鏈也就能夠很清楚的知道了。無外乎內置對象的默認原型鏈,或者自定義構造函數手動修改的原型鏈。
但,初學階段,如果想借助瀏覽器的開發者工具的 console 來測試、查看對象的原型鏈以便驗證猜想,可以這么處理:
var a = []

雖然 _proto_ 可以獲取原型,但拿到的是對象,所以可以借助對象的某些標識,比如原型的 constructor 的 name 函數名屬性標識。
實例

網上關於原型鏈的文章經常會出現這么一張圖片,首先我承認,這張圖很高級,也基本把原型鏈的相關理論表示出來了,但我很不喜歡它。因為對於新手來說,很難看懂這張圖,我第一次看到也一臉懵逼。
就算現在能夠看懂了,我也還是不喜歡它,因為這張圖表達的內容太多了:它不僅表示了某個對象的原型鏈結構,同時,也表示出了實例對象、原型、構造函數三者間的函數,而構造函數本質上也是對象,所以也順便表示它的原型鏈結構。
我們一步步來看,它首先定義了一個構造函數 Foo,然后通過它創建了 f1,f2對象,然后從 f1,f2開始出發,先求他們的原型鏈。
用代碼來說,其實也就是:
function Foo() {}
var f1 = new Foo();
//求f1對象的原型鏈
根據我們上述梳理的理論,很簡單了吧,原型鏈其實也就是:
f1 -> {} -> Object.prototype -> null
接着,它表達了可以用 _proto_ 獲取對象的原型,然后每個原型、構造函數、實例對象三者間的關系它也表達出來了,原型的constructor指向構造函數,而構造函數的prototype指向原型。
而這三個角色本質上也都是對象,既然是對象,那么它們本身也有原型,所以也再順便畫出它們的原型鏈。
總之,就是從 f1 實例對象出發,先找它的原型,通過原型再找構造函數,然后再分別將原型和構造函數看成實例對象,重復之前f1的工作。
另外,又通過 new Object() 創建了對象 o1,求它的原型鏈。
所以,這張圖上,其實表達了一共 5 條原型鏈,分別是:
- f1 的原型鏈
- f1 的原型的constructor指向的構造函數Foo對象的原型鏈
- 函數對象Foo的原型的constructor指向的構造函數Function對象的原型鏈
- f1 的原型的原型即Object.prototype的constructor指向的構造函數Object 對象的原型鏈。
- o1 的原型鏈
如果你能從這張圖看出這5條原型鏈,那么原型鏈的理論你就基本掌握了。
而且,建議看這張圖時,每次都將某條原型鏈跟蹤到底,再去看另一條,這過程不要過多關注在分支上,否則很容易混亂。
對於新手,如果能夠對這張稍作備注,而不是直接將這張圖放出來,我覺得會更好,如下:

大家好,我是 dasu,歡迎關注我的公眾號(dasuAndroidTv),公眾號中有我的聯系方式,歡迎有事沒事來嘮嗑一下,如果你覺得本篇內容有幫助到你,可以轉載但記得要關注,要標明原文哦,謝謝支持~

