在 JavaScript 中,每個Object(以下稱為物件)都有一個隱含的 [[Prototype]]
屬性,它指向這個物件的原型(Prototype)。當試圖訪問物件的某個屬性時,如果這個物件本身沒有該屬性,那麼 JavaScript 引擎會繼續在它的 [[Prototype]]
(aka prototype object) 上找這個屬性。這就是 JavaScript 的原型繼承(Prototypal Inheritance)與原型鏈(Prototype Chain)概念。
附註:這篇文章提到的 Object 都是以 object literal (object initializer) 來創造,不考慮構造函數與 class 的情況。
我們先來看看這段程式碼:
const person = {
name: 'John',
sayName() {
console.log(this.name);
}
}
const student = {
school: 'ABC'
};
// 將 person 設置為 student 的 Prototype Object
Object.setPrototypeOf(student, person);
// 訪問 student 的 Prototype Object person 上的 sayName 方法
student.sayName(); // 輸出 John
上面的程式碼中,我們將 person
設置為 student
的 Prototype Object,這樣 student
就可以access到 person
上的屬性和方法,這就是 JavaScript Prototype Object 的作用,實現物件間的繼承與共享。
Object.setPrototypeOf(student, person)
這是 ES6 提供的設定物件原型的方法。它接受兩個參數:第一個參數是要設置原型的物件、第二個參數是該物件的新原型。所以這行程式碼使 student
物件的隱式原型 [[Prototype]]
被設置為 person
這個 Object。
這樣一來,當訪問 student
的屬性時,如果 student
不存在該屬性,就會去 person
物件上查找。student
物件就可以 access 到 person
的屬性與方法,實現了原型繼承。
看起來很麻煩?為什麼要這樣做?可以繼續往下看,或是跳過這個段落 :
前面提到:
在 JavaScript 中,每個物件都有一個隱含的 [[Prototype]]
屬性,它指向該物件的 Prototype。
但為什麼在上面的程式碼中我們不直接修改它?這是因為[[Prototype]]
是隱含的,無法被我們直接訪問、存取(access)。而__proto__
可以說是這個隱含屬性的 getter/setter 介面(可以 access)。
通過 __proto__
我們可以設置物件的 Prototype Object:
// 通過 __proto__ 設置 Prototype Object
student.__proto__ = person;
student.sayName(); // John
但這種方式會有瀏覽器兼容性的問題,而且是過時的方式,我們不應該使用這個方法來修改prototype object。Object.setPrototypeOf
是更推薦的設定原型的方式。
Object.create()
是 JavaScript 中實現 Prototypal Inheritance 最簡單的方法。
它可以以一個物件為原型,創建一個新的物件。不需要先新增物件後再去修改。可以比較兩種方法:
const person = {
name: 'John'
};
// Object.create() 直接新增一個 student 物件並把他的原型指定為 person
const student1 = Object.create(person);
// Object.setPrototypeOf() 先新增一個 student 物件再將其原型對象指定為 person
const student2 = {};
Object.setPrototypeOf(student2, person);
結果上來說,兩者是幾乎相同的,但在MDN的文件中基於效能考量更建議使用Object.create()
的方法而不是Object.setPrototypeOf(student, person)
Prototype 是 JavaScript 的核心概念之一,是實現繼承的基礎。
[[Prototype]]
指向 Prototype Object__proto__
屬性可以訪問這個隱含的 PrototypeObject.create()
可以簡單實現基於原型的繼承因為篇幅的關係,這篇文章提到的 Object 都是基於 object literal (object initializer) ,不考慮構造函數與 class 的情況,希望未來還有機會寫一篇相關分享~
如果有興趣,可以參考JS基本觀念: 原型鏈(prototype chain) 有提到更多以構造函數來講解原型鏈的概念,或是直接看MDN的文件:Inheritance and the prototype chain。
但因為這篇提到的概念是更底層的邏輯,我想在原理上應該是類似或相同的,只是語法上有所差異。如果喜歡這類的文章歡迎留言跟我說!有任何資訊錯誤也請務必留言告知,謝謝😊