淺談 JavaScript 原型鏈
概述
在 JavaScript 中有種說法叫 “萬物皆物件”,就是說無論是建構函式建立的例項,建構函式本身、原型物件、陣列、函式本質上都是物件,都擁有 __proto__
屬性,即隱式原型,所有函式都擁有 prototype
屬性,即顯式原型(僅限函式),原型物件(prototype
屬性指向的物件),在定義函式時就被建立。
原型鏈指向概述
在 JavaScript 中整個原型鏈及查詢機制用下圖可以完整的表示出來:
原型鏈指向:
1、通過字面量和 new Object()
所建立的物件,他們是建構函式是 function Object()
Object
建構函式的 prototype
指向原型物件 Object.prototype
,Object.prototype
的 constructor
指向建構函式 Object
,而例項的 __proto__
也指向 Object.prototype
,Object.prototype
的 __proto__
指向 null
,所以 Object.prototype
也叫做頂級原型物件。
2、上圖中 new Foo()
function Foo()
的例項,Foo
的 prototype
指向原型物件 Foo.prototype
,Foo.prototype
的 constructor
指向建構函式 Foo
,而例項的 __proto__
也指向 Foo.prototype
,並且 Foo.prototype
雖然是原型物件,但也是物件,所以是建構函式 Object
的例項,__proto__
指向頂級原型物件 Object.prototype
3、陣列的建構函式是 function Array()
原型鏈的指向與其他除 Object
以外的建構函式相同,Array.prototype
的 __proto__
也指向頂級原型物件 Object.prototype
,每一個數組都是 Array
的例項,__proto__
都指向 Array.prototype
。
4、Object
、Array
、Foo
等建構函式的本質也是物件,他們的建構函式是 function Function()
,Function
的 prototype
指向 Function.prototype
,Function.prototype
的 constructor
指向 Function
,所有的建構函式的 __proto__
都指向 Function.prototype
,包括 Function
本身,也就是說建構函式 Function
是由自己構造的,Function.prototype
的 __proto__
同樣指向頂級原型物件 Object.prototype
。
prototype 原型物件
prototype
是函式的一個屬性,屬性的值指向了一個物件,所以,只有函式才有 prototype
原型物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function Person(name, age) { this.name = name; this.age = age; } typeof Person.prototype; // object Person.prototype.constructor; // Person {} Person.prototype.job = "qianduan"; var p1 = new Person("panda", 18); var p2 = new Person("shen", 20); p1.constructor.prototype; // 例項物件查詢建構函式原型物件的方法 |
一般會把物件共有的屬性和方法都放在建構函式的原型物件上。
例項、建構函式、原型物件的關係
建構函式的原型 prototype
屬性指向一個原型物件,例項也可以通過 __proto__
指向原型物件,但本質上例項和建構函式之間是沒有關係的。
1 2 3 4 5 6 7 8 |
function Person(name, age) { this.name = name; this.age = age; } var p = new Person("nihao", 16); p.constructor = { name: "haha" }; p.name; // nihao |
上面的程式碼中改變了建構函式的值為一個物件,物件中的屬性 name
並沒有影響例項的 name
屬性值。
例項屬性 __proto__
上面訪問例項 p
的原型,實際使用 p.constructor.prototype
去找原型物件,當建構函式的值改變後是找不到原型物件的,所以例項並不是通過 constructor.prototype
去查詢原型物件的,而是通過每一個例項都有的 __proto__
屬性,這個屬性指向建立例項的建構函式原本的原型物件,這個屬性不是標準,在 IE 下不存在。
1 2 3 4 5 6 7 8 9 |
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.job = "qianduan"; var p = new Person("nihao", 16); p.__proto__.job; // qianduan |
當建構函式的 prototype
屬性值被改變之後,在之前建立的例項的 __proto__
屬性值的仍然引用原型物件,所以對建構函式改變前建立的例項是沒有影響的,會影響後面建立的例項。
原型鏈查詢機制
例項物件在呼叫了一個屬性或方法時,如果物件本身沒有這個屬性或方法,會去自己的原型物件查詢,也就是 __proto__
中查詢,如果原型物件中沒有,去原型物件的原型物件查詢,一般(原型鏈沒有被修改)情況下就是去 __proto__
的 __proto__
中查詢,即頂級原型物件 Object.prototype
,如果例項物件本身有這個屬性,則直接輸出,不再向上查詢,如果物件本身和原型物件具有同名屬性,則會遮蔽掉原型物件的屬性。
1 2 3 4 5 6 7 8 9 10 11 12 |
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.job = "qianduan"; var p = new Person("nihao", 16); p.job; // qianduan p.job = "houtai"; p.job; // houtai p.__proto__.job; // qianduan |
總結
原型鏈的指向及原型鏈的查詢機制是 JavaScript 中非常重要的基礎知識,理解原型鏈是更深入瞭解繼承和麵向物件程式設計的必經之路。