JS基本觀念: 原型鏈(prototype chain)
之前在寫JS基本觀念:typeof vs instanceof時,有稍微帶到一些原型鍊,不過當初沒特別去講解。最近我回過頭來檢視自己對於原型鏈的理解後發現我也沒說非常的了解原理,所以經過一些survey後有了這篇我對於原型鏈的理解。
前言
原型鍊(prototype chain)在javascript中一直是很重要的一環,不過也讓人挺不好理解的,尤其裡面一堆專有名詞,例如說prototype
, __proto__
, constructor
, Object.prototype
, Function.prototype
, new
等等。
光是看都快昏了! 但偏偏你又避不了原型鏈,他也是面試必考題之一,所以還是來好好面對它吧!
Javascript中的class
以下這篇是很好的切入點,講到了當初開發javascript這語言的工程師當初是怎樣設計prototype的。挺建議先看過的。
首先,javascript不像java之類的物件導向語言有class的概念(es6的class只是語法糖),不過他也設計出了類似的繼承機制。
如果你今天是用java的class來產生一個instance的話,你會這樣寫
Person p = new Person();
於是javascript就把 new
也引用進來,不過new後面接的是class,但javascript沒有class啊?那我們該接什麼呢?
這時候工程師就想到,如果是以java來看的話,java在new instance時會呼叫class的constructor,也就是構建函數,所以javascript就做了個簡化,就在後面直接接了構建函數而不是class。
所以,看到以下程式碼
// constructor
function Person(name, age) {
this.name = name;
this.age = age;
}var personA = new Person('Charles', '30');
var personB = new Person('Jane', 29);
console.log(personA.name); // Charles
console.log(personB.name); // Jane
這邊Person
就是一個構造函數,表示對象的原型,而javascript利用new
+ 後面的構造函數就能創造出了一個Person
的實例。
new的缺點
延續上面的程式碼,可以很清楚地知道,personA
& personB
都是獨立的instance,裡面的name
& age
都是獨立且不互相干擾的。
比如我修改了personA
的age
,此時personB
的age
仍然是29。
personA.age = 20;
console.log(personB.age); // 29
同樣的,若我今天在Person
當中新增一個function。
function Person(name, age) {
this.name = name;
this.age = age;
this.log = function () {
console.log(this.name + ', age:' + this.age);
}
}
var personA = new Person('Charles', '30');
var personB = new Person('Jane', 29);
personA.log(); // Charles, age: 30
personB.log(); // Jane, age: 29console.log(personA.log === personB.log) // false
從上面可以發現log
這個function也是各自獨立的,這就造成了相同功能卻佔據了兩份資源的狀況。
所以單純用new
建立出來的instance會有無法共用屬性與資源浪費的狀況。
原型鏈的引入
為了解決上面碰到的共享問題,工程師替構造函數加入了原型鏈(prototype
)的屬性。所有要共享的屬性與方法,就會被放在原型鏈內,而想要各自擁有的就放在構造函數內。當instance被建立後,會自動引用對應的原型鏈內所有的屬性與方法。
讓我們來改寫下上面的例子吧
function Person(name, age) {
this.name = name;
this.age = age;
}Person.prototype.log = function() {
console.log(this.name + ', age:' + this.age);
}var personA = new Person('Charles', '30');
var personB = new Person('Jane', 29);
console.log(personA.log === personB.log) // true// 功能依舊
personA.log(); // Charles, age: 30
personB.log(); // Jane, age: 29
OK,我們成功地把log
方法抽出來變成共用的了,同時如果你修改log
方法,會發現所有instance都一併被影響了。
Person.prototype.log = function() {
console.log('Name: ' + this.name + ', age:' + this.age);
}
personA.log(); // Name: Charles, age: 30
personB.log(); // Name: Jane, age: 29
原型鏈的運作
剛剛上面所說的都是跟你說,嘿,你有一個建構函數並且有prototype
可用呢,但你有想過到底是怎樣運作的麼?
以上面的 var personA = new Person(‘Charles’, ‘30’);
為例,呼叫personA.log()
時,javascript到底是怎樣找到那個log
function的呢?
personA
這個instance本身是沒有log
這個function的,依照javascript的機制,personA
屬於Person
的一個實例,所以如果在personA
找不到,那我們會去Person.prototype
那找。那中間到底是怎樣讓personA
連結到Person.prototype
呢?
答案是, __proto__
(REF: MDN)
function Person(name, age) {
this.name = name;
this.age = age;
}Person.prototype.log = function() {
console.log(this.name + ', age:' + this.age);
}var personA = new Person('Charles', '30');
console.log(personA.__proto__ === Person.prototype) // true
personA
的__proto__
會指到Person.prototype
,所以在personA
沒找到log function,javascript就會透過__proto__
往Person.prototype
去找。
那假如Person.prototype
裡也還是沒有log
function呢?那javascript會依照同樣邏輯,往Person.prototype.__proto__
去找,依此類推,直到某一次的__proto__
指到null為止。
而上面這一條透過__proto__
不斷串起來的鍊,就叫做原型鍊。透過這一條原型鍊,就可以達成類似繼承的功能,可以呼叫自己 parent 的 method。
以下以幾個例子來看看原型鏈吧!希望能讓你更明白怎樣一路往上串的。
function Person(name, age) {
this.name = name;
this.age = age;
}Person.prototype.log = function() {
console.log(this.name + ', age:' + this.age);
}var personA = new Person('Charles', '30');
console.log(personA.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
如果想知道一個屬性是存在於instance本身,還是存在於原型鏈當中,可以使用hasOwnProperty
這個function。
function Person(name, age) {
this.name = name;
this.age = age;
}Person.prototype.log = function() {
console.log(this.name + ', age:' + this.age);
}var personA = new Person('Charles', '30');
console.log(personA.hasOwnProperty('log')); // false
console.log(personA.__proto__.hasOwnProperty('log')); // true
END
一路也看了不少文章,感謝這些文章帶給我的成長,我這篇不是什麼新東西或知識了,對我來說也是個學習紀錄,如果有緣能看到這篇,希望也同樣能給你點收穫!