在 ES6 之前,JavaScript 是沒有 Class 的用法( ES6 的 Class 也只能算是語法糖 ),所以要用 prototype 來實現共用方法,而且 Prototype 也可以拿來解決一些問題,看以下例子:
function Person(name) {
this.name = name;
this.getName = function(){
return this.name;
}
}
const nick = new Person('nick');
const peter = new Person('peter');
console.log(nick.getNmae === peter.getName); // => false
可以看得出來儘管兩個 instance 都是回傳 function(){ return this.name; }
內容,但因為 instance 的記憶體位置不同,會被視為不同的方法。
所以為了要解決以上狀況,就可以用 prototype 來實現
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
const nick = new Person('nick');
const peter = new Person('peter');
console.log(nick.getName === peter.getName); // => true
而為什麼 prototype 就可以讓 method 相等呢?
JS 的運作原理是當我們執行 nick.getName
的時候,會先去看 nick
這個 instance 有沒有 getName
這個 method,以上例子在宣告 Person
這個 Class 是沒有的。
那下一步呢,就是看 Person
的原型 prototype
裡面找,就發現有 getName
這個方法,而 nick
跟 peter
這兩個 instance 是共用 Class 的 prototype 方法,所以才會回傳 true
。
console.log(nick.getName === Person.prototype.getName); // => true
推薦先閱讀這兩篇文章:Javascript继承机制的设计思想、从设计初衷解释 JavaScript 原型链
可以先從為什麼這樣設計 JavaScript 出發,當初設計語言並沒有 Class,所以只能用建構函式 function( 可以當作 constructor
),所以就可以知道 prototype 的出現是為了達到所有 instance 都能共享 property
& method
。
JavaScript 一開始設計的時候,所有皆物件 Object,所以需要一個機制來連接物件,也進而達成繼承功能,實現 Instance 的方法是透過 new 出一個建構函式。
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log('Object', this.name, 'Hi'); // => new 出幾份 instance 就會有幾份,佔用記憶體空間
}
}
Person.prototype.sayHi = function(){
console.log('Object', this.name, 'Hi'); // => 用 prototype 實現共享 property & method
}
這是怎麼達成這機制的呢?
.__protp__
: 隱藏的指路人當我們創建一個 instance 的時候,JS 會自動幫你加上一個 .__proto__
的屬性,作用是告訴程式,當我們在 Class 找不到 method 的時候,要去哪裡找,算是一個 指引 的概念
console.log(nick.__proto__ === Person.prototype); // => true;
console.log(nick.__proto__.getName === Person.prototype.getName); // => true;
而 .__protp__
這個指引的屬性可以省略,所以以下才會成立
console.log(nick.getName === Person.prototype.getName); // => true
在使用 new 的同時,instance (peter) 會自動加上 .__proto__
,並且指向建構函式 (Person) 的 .prototype
。
peter.__proto__ 指向 => Person.prototype
nick.__proto__ 指向 => Person.prototype
而建構函式 Person
其實也是 Object new
出來的啊,所以 Person.prototype 也是會有 .__proto__
,同樣指向 Object.prototype
=> Person.__proto__ === Person.prototype
=> Person.__proto__.__proto__ === Object.prototype
=> Person.prototype.__proto__ === Object.prototype
=> Object.prototype === null
1. Person 是 function 的 instance
=> 可以想像成是 var Person = new Function();
=> Person.__proto__ === Function.prototype
2. function 是 Object 的 instance
=> 可以想像成是 var Function = new Object();
=> Function.prototype.__proto === Object.prototype
=> Person.__proto__.__proto__ === Object.prototype
所以可以看得出來, peter (instance
) 的原型會指向 Person (constructor
),而 Person (constructor
) 的原型又會指向 Object,這樣鍊型的關係達到了繼承的效果,而又稱為原型鍊。
console.log(nick.getName());
所以要怎麼找到 getName
這個 method 呢?
nick
nick.__proto__
Person.prototype
)nick.__proto__.__proto__
Person.prototype.__proto__
)Object.prototype
)nick.__proto__.__proto__.__proto__
=> 最上層,也就是 null
Person.prototype.__proto__.__proto__
)Object.prototype.__proto__
)console.log(nick.getName === Person.prototype.getName); // => true
console.log(nick.__proto__ === Person.prototype); // => true;
console.log(nick.__proto__.__proto__ === Person.prototype.__proto__); // => true;
console.log(nick.__proto__.__proto__ === Object.prototype); // => true;
console.log(Object.prototype.__proto__); // => null;
所以 JS 會一直往上找,一但找到該 method 就會停止。
所以如果 Person
跟 Object
的 prototype 都有 getName
,真正回傳的會是 Person
的 getName
,因為 Person 的尋找優先順序比較高。
所以這樣 一層一層往上查找,就構成一條原型鍊 prototype chain,而這條鍊的最頂層就是 Object.prototype.__proto__
也就是 null
。
注意: 以上都是了解 JS 底層實作的概念,實務上盡量不要去用到
.__proto__
這個屬性。
可以再看下一個例子,確定自己清楚 prototyp 的尋找順序:
function Person(name) {
this.name = name;
this.sayHi = function () {
console.log(this.name, 'Hi => method of instance'); // => new 出幾份 instance 就會有幾份,佔用記憶體空間
}
}
Person.prototype.sayHello = function () {
console.log(this.name, 'Hello => method of Class'); // => 用 prototype 實現共享 property & method
}
Object.prototype.sayHello = function () {
console.log(this.name, 'Hello => method of Object'); // => method 跟 Person 重複,所以找到 Person 就停了
}
Object.prototype.sayYo = function () {
console.log(this.name, 'Yo => method of Object');
}
const peter = new Person('peter');
peter.sayHi(); // peter Hi => method of instance
peter.sayHello(); // peter Hello => method of Class
peter.sayYo(); // peter Yo => method of Object
// => 這樣才可以使用到 Object 的 method
Object.sayHello.call(peter); // peter Hello => method of Object
hasOwnProperty
: 確認是否為 instance 自己的方法peter.hasOwnProperty(sayHi); // => true
peter.hasOwnProperty(sayHello); // => false
而當我們寫下 peter.sayHello
的時候, .sayHello
這個 method 並不在 peter
這個 instance 上面,就會自動往上一層查找 => Person.prototype
所以如果想知道該 method 是在 instance 上,還是存在於原型鍊中,可以用 hasOwnProperty
這個方法確認
instanceof
: 確認 A 是否為 B 的 instancepeter instanceof Person; // true
Person instanceof Function; // true
Person instanceof Object; // true
// Function 跟 Object 互為對方的 intance,好奇妙
Function instanceof Object; // true
Object instanceof Function; // true
constructor
: 建構函式每一個 prototype 都有個 constructor
屬性,而想當然爾就是指向自己(建構函式)囉。
peter.constructor === Person; // true;
Person.prototype.constructor === Person; // true
Person.prototype.hasOwnProperty('constructor'); // true
( 以上內容大部分是 程式導師實驗計畫第三期 的學習筆記,如有錯誤歡迎糾正,非常感謝 🤓 )
Written on August 10th, 2019 by Yakim shu