等這一天好久了。
簡述
在 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
本身沒有 getName
,所以會透過 __proto__
去找到上一層 prototype:
1 2 3 4
| console.log(peanu.__proto__);
console.log(peanu.__proto__ === Person.prototype);
|
所以背後就是透過 __proto__
來找到 Person.prototype
再找到 getName
的。
但「往上找」這個動作其實能做很多次,所以它的完整流程會是這樣:
- peanu 有沒有 getName
- 沒有?那
peanu.__proto__
有沒有 getName => Person.prototype
- 沒有?那
peanu.__proto__.__proto__
有沒有 getName => Object.prototype
- 沒有?那
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.prototype.getName = function () { console.log('Object', this.name); }
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]) { return instance.__proto__[methods].call(instance); } return prototypeChain(instance.__proto__, methods); }
const peanu = new Person('peanu');
prototypeChain(peanu, 'getName');
|
這邊同時在 Object
和 Person
都綁上 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')) console.log(peanu.__proto__.hasOwnProperty('getName'))
|
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); console.log(peanu instanceof Object); console.log(peanu instanceof Array); console.log(peanu instanceof Function);
|
這邊也自己實作了一下:
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)); console.log(myinstanceof(peanu, Object)); console.log(myinstanceof(peanu, Array)); console.log(myinstanceof(peanu, Function));
|
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');
console.log(peanu.constructor === Person); console.log(peanu.hasOwnProperty('constructor'))
console.log(Person.prototype.constructor === Person); console.log(Person.prototype.hasOwnProperty('constructor'))
|