原型( Prototype )
每個 JavaScript 物件都有一個原型物件,它作為該物件繼承屬性和方法的基礎。物件的原型可以通過 __proto__
屬性取用 ( ES6 開始使用 Object.getPrototypeof()、Object.setPrototypeof() 這兩個 accessors 取用 ),原型物件也是一個物件,有自己的原型。當一個物件呼叫一個方法或取用一個屬性時,它會先查找自身是否有該屬性或方法,如果沒有,則會透過原型鏈查找。透過原型,物件可以繼承來自其原型的屬性和方法。
原型鏈(Prototype Chain)
原型鏈是由原型物件組成的一個鏈,用於查找物件的屬性和方法。當一個物件取用一個屬性或方法時,它會先查找自身是否有該屬性或方法,如果沒有,則會查找其原型物件是否有該屬性或方法,如果還沒找到,則會繼續查找其原型的原型,直到找到為止。
以下是一個簡單的例子,說明嘗試存取屬性時會發生的事:
// 利用含有 a 與 b 屬性的 f 函式,建立一個 o 物件:
let f = function () {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a: 1, b: 2}
// 接著針對 f 函式的原型添加屬性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要寫 f.prototype = {b:3,c:4}; 因為它會破壞原型鏈
// o.[[Prototype]] 有 b 與 c 的屬性:{b: 3, c: 4}
// o.[[Prototype]].[[Prototype]] 是 Object.prototype
// 最後 o.[[Prototype]].[[Prototype]].[[Prototype]] 成了 null
// 這是原型鏈的結末,因為 null 按照定義並沒有 [[Prototype]]。
// 因此,整個原型鏈看起來就像:
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
console.log(o.a); // 1
// o 有屬性「a」嗎?有,該數值為 1。
console.log(o.b); // 2
// o 有屬性「b」嗎?有,該數值為 2。
// o 還有個原型屬性「b」,但這裡沒有被訪問到。
// 這稱作「property shadowing」。
console.log(o.c); // 4
// o 有屬性「c」嗎?沒有,那就找 o 的原型看看。
// o 在「o.[[Prototype]]」有屬性「c」嗎?有,該數值為 4。
console.log(o.d); // undefined
// o 有屬性「d」嗎?沒有,那就找 o 的原型看看。
// o 在「o.[[Prototype]]」有屬性「d」嗎?沒有,那就找 o.[[Prototype]] 的原型看看。
// o.[[Prototype]].[[Prototype]] 是 Object.prototype,預設並沒有屬性「d」,那再找他的原型看看。
// o 在「o.[[Prototype]].[[Prototype]].[[Prototype]]」是 null,停止搜尋。
// 找不到任何屬性,回傳 undefined。
函式建構子( Constructor )
函式建構子是一種用來創建物件的函式,它使用 new
關鍵字創建一個新物件,並將該物件的原型設置為函式的 prototype
屬性。函式建構子也可以添加屬性和方法,透過原型,這些屬性和方法可以被所有由該函式建構的物件所共享。
因此當我們這樣寫時:
let o = new Foo();
JavaScript 其實會做:
let o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);
物件屬性特徵(Object Property Descriptor)
JavaScript 中的每個屬性都有一個物件,稱為屬性描述符(Property Descriptor),它描述了該屬性的特徵。物件屬性特徵描述了一個屬性的值 (value)、可枚舉性 (enumerable)、可寫性 (writable)、可配置性 (configurable) 等屬性。
-
value
:該屬性的值。預設值為undefined
。 -
writable
:如果為true
,則該屬性的值可以被修改。如果為false
,則該屬性的值無法被修改。預設值為true
。 -
enumerable
:如果為true
,則該屬性可以被for...in
迴圈枚舉。如果為false
,則該屬性無法被枚舉。預設值為true
。 -
configurable
:如果為true
,則該屬性的特徵可以被修改,並且該屬性可以被刪除。如果為false
,則該屬性的特徵無法被修改,且該屬性無法被刪除。預設值為true
。
以下是一個修改屬性特徵的範例:
let obj = {name: 'John'};
let descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// {value: "John", writable: true, enumerable: true, configurable: true}
// 修改屬性特徵
descriptor.writable = false;
descriptor.configurable = false;
Object.defineProperty(obj, 'name', descriptor);
// 現在 name 屬性已變為不可寫和不可配置
obj.name = 'Bob';
delete obj.name;
console.log(obj); // {name: "John"},不可修改和刪除
Class
Class 是 ES6 中引入的一個新特性,它提供了一種更簡潔的方式來定義物件的屬性和方法。Class 本質上是一種特殊的函式建構子,它使用 class
關鍵字定義,並使用 constructor
方法定義屬性和方法。與傳統的函式建構子不同,class 定義的方法預設是不可枚舉的。
以下是一個範例說明 class 定義的方法預設是不可枚舉的:
class MyClass {
constructor(a, b) {
this.a = a;
this.b = b;
}
sum() {
return this.a + this.b;
}
}
const myObj = new MyClass(2, 3);
for (let key in myObj) {
console.log(key); // Output: a, b
}
console.log(Object.keys(myObj)); // Output: [ 'a', 'b' ]
Top comments (0)