理解原型鍊的運作

等這一天好久了。

簡述

ES5 實作物件導向 裡有提到透過「prototype」可以讓每個 instance 參考到同一個 function。但並沒有解釋實際上是怎麼做到的?所以這邊就來解釋一下。

首先有個東西叫做 __proto__,它的作用就是:

如果在這個 instance 上找不到,就去它的 prototype 找找看。

來舉個例子:

1
2
3
4
5
6
7
8
9
10
11
12
function Person (name) {
this.name = name;
}
Person.prototype.getName = function () {
console.log(this.name);
}
Person.prototype.setName = function (newName) {
this.name = newName;
}

const peanu = new Person('PeaNu');
console.log(peanu.getName); // PeaNu

按照前面所說,peanu 本身沒有 getName,所以會透過 __proto__ 去找到上一層 prototype:

1
2
3
4
// Person: {getName:  [Function (anonymous)]}
console.log(peanu.__proto__);
// true
console.log(peanu.__proto__ === Person.prototype);

所以背後就是透過 __proto__ 來找到 Person.prototype 再找到 getName 的。

但「往上找」這個動作其實能做很多次,所以它的完整流程會是這樣:

  1. peanu 有沒有 getName
  2. 沒有?那peanu.__proto__ 有沒有 getName => Person.prototype
  3. 沒有?那 peanu.__proto__.__proto__ 有沒有 getName => Object.prototype
  4. 沒有?那 peanu.__proto__.__proto__.__proto__ 有沒有 getName => null

這一連串的過程就稱為「Prototype chain(原型鍊)」,因為透過 __proto__ 一直往上找,往上找,往上找。就跟 Scope chain 的概念是一樣的。

當原型鍊找到頂的時候就會回傳 null,所以只有在都找不到的情況下才會噴 Error。

實作原型鍊

理解上面的觀念後,就可以來實作這個流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Person (name) {
this.name = name;
}
// 綁在 Object 上
Object.prototype.getName = function () {
console.log('Object', this.name);
}
// 綁在 Person 上
Person.prototype.getName = function () {
console.log('Peson', this.name)
}

function prototypeChain (instance, methods) {
// 到頂了
if (instance.__proto__ === null) {
throw new Error('Can not find methods');
}
// 找到了
if (instance.__proto__[methods]) {
// 用 call 來呼叫,並且讓 this 指向 instance
return instance.__proto__[methods].call(instance);
}
return prototypeChain(instance.__proto__, methods);
}

const peanu = new Person('peanu');

prototypeChain(peanu, 'getName');

這邊同時在 ObjectPerson 都綁上 getName 是為了模擬「往上找」這個動作。

當兩個同時存在時, Person 會先被找到,所以會呼叫 Person.prototype.getName;只有 Object 存在時就會呼叫 Object.prototype.getName;如果都不存在,最後會找到頂,執行 throw new Error

這個就是原型鍊的流程,做完後感覺更理解了。

最後給個小範例,讓你更清楚每個資料型別對應的 prototype 是誰:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person () {}
const obj = {};
const arr = [];
const str = '123';
const bool = false;
const num = 123;

console.log(Person.__proto__ === Function.prototype);
console.log(obj.__proto__ === Object.prototype);
console.log(arr.__proto__ === Array.prototype);
console.log(str.__proto__ === String.prototype);
console.log(bool.__proto__ === Boolean.prototype);
console.log(num.__proto__ === Number.prototype);

其他和原型鍊有關的 method

hasOwnProperty

用來檢查一個 method 是在 instance 身上還是在 prototype 身上:

1
2
3
4
5
6
7
8
9
10
function Person (name) {
this.name = name;
}
Person.prototype.getName = function () {
console.log(this.name);
}
const peanu = new Person('peanu');

console.log(peanu.hasOwnProperty('getName')) // false
console.log(peanu.__proto__.hasOwnProperty('getName')) // true

instanceof

檢查 A 是不是 B 的 instance:

1
2
3
4
5
6
7
8
9
function Person (name) {
this.name = name;
}

const peanu = new Person('peanu');
console.log(peanu instanceof Person); // true
console.log(peanu instanceof Object); // true
console.log(peanu instanceof Array); // false
console.log(peanu instanceof Function); // false

這邊也自己實作了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person (name) {
this.name = name;
}

const peanu = new Person('peanu');

function myinstanceof (instance, compare) {
if (instance.__proto__ === null) return false
if (instance.__proto__ === compare.prototype) return true
return myinstanceof(instance.__proto__, compare);
}

console.log(myinstanceof(peanu, Person)); // true
console.log(myinstanceof(peanu, Object)); // true
console.log(myinstanceof(peanu, Array)); // false
console.log(myinstanceof(peanu, Function)); // false

constructor(屬性)

每個 prototype 都會有 constructor 屬性,這個值就是建構函式自己:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person (name) {
this.name = name;
}
Person.prototype.getName = function () {
console.log(this.name);
}
const peanu = new Person('peanu');


// 如果是透過 instance 來找的話,實際是透過原型鍊來往上找的
console.log(peanu.constructor === Person); // true
console.log(peanu.hasOwnProperty('constructor')) // false

console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.hasOwnProperty('constructor')) // true;
new 實際上在做什麼? ES5 實作物件導向
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×