Prototype & _proto_ (dunder proto)
► prototype
(原型)是真實的物件,後面可以接方法(methods)。
►__proto__
是物件的內部屬性,指向其原型。
那他們之間的關係是?
以下範例來看,game1
與game2
的__proto__
就是指向pcGame.prototype
。JavaScript透過__proto__
去找到pcGame.prototype
,發現裡面有rate
這個方法。
function pcGame(name, genre){
this.name = name;
this.genre = genre;
this.score = null;};
pcGame.prototype.rate = function(score){
this.score = score;
}
let game1 = new pcGame('Age of Empires 3', 'Real-time strategy');
let game2 = new pcGame('The Outer Worlds', 'Action role-playing');
game1.__proto__ === pcGame.prototype;
game2.__proto__ === pcGame.prototype;
//true
//true
如果找不到方法的話,JavaScript就會往pcGame.prototype
的上一層pcGame.prototype.__proto__
繼續找,直到找到null
才會停止。
pcGame.prototype.__proto__ === Object.prototype
pcGame.prototype.__proto__.__proto__ === Object.prototype.__proto__
Object.prototype.__proto__ === null
//true
//true
//true
Prototype Chain
►像上述那種一層層串起來的就是Prototype Chain。
以下範例,創建一個函式pcGame
,我們可以用new
去取用函式rate
,但會發現game1
與game2
使用的函式rate
不是同一個,而是重複建立出來的。
function pcGame(name, genre){
this.name = name;
this.genre = genre;
this.rate = function(score){
this.score = score;};
let game1 = new pcGame('Age of Empires 3', 'Real-time strategy');
let game2 = new pcGame('The Outer Worlds', 'Action role-playing');
game1.rate === game2.rate;
//false
要改善這點,我們可以把函式rate
放在pcGame
的property
裡,也就是pcGame的原型
,這樣就不用每次都建立新的方法,而是可以共用同一個方法。
function pcGame(name, genre){
this.name = name;
this.genre = genre;
this.score = null;};
pcGame.prototype.rate = function(score){
this.score = score;
}
let game1 = new pcGame('Age of Empires 3', 'Real-time strategy');
let game2 = new pcGame('The Outer Worlds', 'Action role-playing');
game1.rate === game2.rate;
//true
Constructor
►new
一個物件時會執行的函式。
constructor
是一個函式,它創立一個物件的繼承(instance)。在JavaScript中,當我們使用new
關鍵字去宣吿(declare)一個物件時,將呼叫constructor
。換句話說,只要使用new
去宣告物件後呼叫的函式,我們就可以看做是constructor function
。
呼叫constructor時發生了什麼?
①使用new創立一個新的物件。
②tihs
指向我們新new出來的物件。
③將物件的__proto__
指向constructor
的prototype
,形成原型鏈。
④回傳物件。
模型圖片
這邊繪製了整個關係的圖,可以更好理解運作:
如何檢查屬性是不是屬於當前物件?
有三種方式可以檢查:
1. hasOwnProperty()
2. instanceof
3. isPrototypeOf()
hasOwnProperty()
語法
obj.hasOwnProperty(prop)
按照前面給的程式範例來驗證看看,發現確實rate
不是game1
原本就有的屬性,而是存在於原型鏈中。
game1.hasOwnProperty('name');
//true
game1.hasOwnProperty('rate');
//false
hasOwnProperty()的限制
hasOwnProperty()
無法檢查整條原型鏈,它只會檢查輸入的物件。要改善這點,可以利用for in
來遍歷檢查。
function pcGame(name, genre){
this.name = name;
this.genre = genre;
this.score = null;};
pcGame.prototype.rate = function(score){
this.score = score;}
let game1 = new pcGame('Age of Empires 3', 'Real-time strategy');
let game2 = new pcGame('The Outer Worlds', 'Action role-playing');
for(let i in game1){
if(game1.hasOwnProperty(i)){
console.log(`自身屬性 ${i}`)}else
console.log(`繼承自其他屬性 ${i}`)
}
//自身屬性 name
//自身屬性 genre
//自身屬性 score
//繼承自其他屬性 rate
instanceof
instanceof
用於檢查constructor
的prototype
屬性有沒有出現在該物件的原型鏈上。
語法:
object instanceof constructor
實例:
game1 instanceof pcGame; //true
game2 instanceof pcGame; //true
game1 instanceof Object; //true
game2 instanceof Object; //true
pcGame instanceof Object; //true
pcGame instanceof Function; //true
game1 instanceof Function; //false
game2 instanceof Function; //false
因為game1
繼承的是pcGame.prototype
。所以game1
是繼承Object
而不是Function
。
game1.__proto__ === pcGame.prototype; //true
pcGame.prototype.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true
isPrototypeOf()
isPrototypeOf()
是一個方法(method),可以幫忙檢查物件有沒有存在於該物件的原型鏈上。
語法:
prototypeObj.isPrototypeOf(object)
實例:
pcGame.prototype.isPrototypeOf(game1); //true
pcGame.prototype.isPrototypeOf(game2); //true
參考資料:
- proto VS. prototype in JavaScript
- 你懂 JavaScript 嗎?#19 原型(Prototype)
- 該來理解 JavaScript 的原型鍊了
- What is a constructor in JavaScript?
- [筆記] 了解JavaScript中原型(prototype)、原型鍊(prototype chain)和繼承(inheritance)的概念
- instanceof - MDN
- Object.prototype.isPrototypeOf() - MDN
- Object.prototype.hasOwnProperty() - MDN
- js中的hasOwnProperty()和isPrototypeOf()