Ch. 8 物件
語法
宣告式 (declarative)、字面值 (literal):
var myObj = {
key: value;
}
建構式 (constructed form):
var myObj = new Object(); myObj.key = value;
型別
- 六種型別:string、number、boolean、null、undefined、object
- null 不是 object
內建物件
string、number、boolean、object、function、Array、Date、RegExp、Error
這些其實是內建的函式,可以被作為建構器搭配 new 來呼叫。
基本型別值 (eg. var str = "I am a string";) 並不是一個物件,當在上面進行操作 (如取得長度、存取個別字元等) 時 JS 會將其強制轉型為一個物件。
- null 與 undefined 沒有物件包裹器的形式。
- Date 沒有對應的字面值形式。
- object、function、Array、RegExp 習慣上使用簡便的字面值形式,除非需要額外的設定時,才使用建構形式。
- Error 會在被擲出時自動被創建。
內容
物件的內容由任意型別的值 (values) 所構成,值被儲存在命名過的位置中,稱之為特性 (properties)。物件內容中只會儲存特性名稱 (property names),名稱指向值儲存之處 (references)。
var myObj = {
a: 2
};
myObj.a; // 特性存取
myObj["a"]; // 鍵值存取,可接受任何相容於 Unicode 的字串
計算得出的特性名稱
ES6 新增 computed property names。可以在物件的 key 中使用運算式。
var prefix = "foo";
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
特性 vs. 方法
在其他語言中,屬於物件 (或類別,class) 的函式,被稱之為方法 (method)。
然而,在 JS 中,函式永遠不會屬於物件,在物件中所宣告的函式也只是對於函式的參考之一。
陣列
- 陣列預設使用數值索引,以非負整數來編號。
- 陣列是一個物件,即使索引值不是非負整數,仍然可以新增,但陣列的長度將不會計算。
- 看起來像數字的索引值會被轉型。
var myArray = [ "foo", 42, "bar" ]; myArray.baz = "baz"; myArray.length; // 3 myArray.baz; // "baz" myArray["3"] = "baz"; myArray.length; // 4 myArray[3]; // "baz"
複製物件
淺層拷貝 (shallow) :基本型別值為複製品,物件則是指向相同的參考。
深層拷貝 (deep):複製所有的內容。
微妙的解法,將物件序列化成 JSON 字串。
var newObj = JSON.parse( JSON.stringify( someObj ) );
ES6 提供了 Object.assign(target, ...source object),它會迭代所有可列舉的 (enumerable)、自有的 (owned) 鍵值,並將它們複製到目標上。
特性描述器
在 ES5 中,所有特性都會以特性描述器 (property descriptor) 來加以描述。
var myObject = {
a: 2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }
我們也可以使用 Object.defineProperty(..) 來新增或修改一個特性。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2
- writable:是否能夠改變一個特性的值。
- configurable:是否能使用
Object.defineProperty(..)修改描述器定義。
當 configurable 為 false 時:
1. writable 還是可以從 true 被改為 false;
2. 禁止使用 delete 刪除特性。 - enumerable:是否為可列舉的。執行
for..in或Object.keys(..)等列舉動作時,該特性將不會出現。
不可變性
- 物件常數:使用
writable: false和configurable: false能建立出無法被變更、不可重定義、不可刪除的常數。 - 避免擴充:呼叫
Object.preventExtensions(..)可防止新的特性被加到物件上。 - 密封:
Object.seal(..)= 避免擴充 +configurable: false,不可新增、不可重新配置或刪除。 - 凍結:
Object.freeze(..)= 密封 +writable: false,不可新增、不可重新配置或刪除,並且不可更改值。
// Object.preventExtensions(..)
var myObject = {
a: 2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
[[Get]]
特性存取不只會在物件中尋找特性,還會巡訪 [[Prototype]] 串鏈,如果無法藉由任何方法找到該特性,會回傳 undefined。
[[Put]]
演算法會大致檢查:
- 該特性是一個存取器描述器嗎?若是,就呼叫 Setter。
- 該特性是 writable 為 false 的資料描述器嗎?若是,就會設值失敗。
- 否則,按照正常方式將值設定給特性。
getter 與 setter
[[Get]] 與 [[Put]] 控制了值如何被設定和取得的預設行為,如果要覆寫這些行為,可以透過定義 getter 或 setter 來達成。
var myObject = {
// define a getter for `a`
get a() {
return this._a_;
},
// define a setter for `a`
set a(val) {
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a; // 4
存在
詢問物件是否擁有某個特性,in 會察看特性名稱是否在物件中,或是更高層的 [[Prototype]] 串鏈;hasOwnProperty(..) 只會檢查該物件。
var myObject = {
a: 2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
列舉
propertyIsEnumerable(..)會測試給定的特性名稱是否直接存在於物件上,並且是可列舉的 (enumerable: true)。Object.keys(..)回傳所有可列舉特性所構成的陣列。Object.getOwnPropertyNames(..)回傳所有特性構成的陣列。- 上述兩個方法都只會巡列給定的物件。
迭代
ES5/ES6 提供了多個陣列迭代輔助器,如
- Array.forEach(..):迭代陣列中的所有值。
- Array.every(..):迭代所有值,直到 callback 回傳一個 false。
- Array.some(..):迭代所有值,直到 callback 回傳一個 true。
- for..of (ES6):直接迭代陣列中的值。
// Array.some(..)
const list = [
{
name: 'Winter',
age: 20,
},
{
name: 'Jeff',
age: 28,
},
{
name: 'Cindy',
age: 13,
},
];
const result = list.some((person, index, array) => {
console.log(person, index, array);
return person.age > 25;
});
console.log(`result: ${result}`);
// for..of
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3
物件沒有內建的 @@iterator (迭代器),可以為物件定義自己的迭代器,並搭配 ES6 的 for..of 做使用。
var myObject = {
a: 2,
b: 3
};
Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys( o );
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
} );
// iterate `myObject` with `for..of`
for (var v of myObject) {
console.log( v );
}
// 2
// 3
複習
- JS 中物件具有字面值形式和建構形式,多數情況下優先使用字面值形式,但建構形式能夠提供更多的創建選項。
- JS 中有六種基本型別。
- 物件是由 key / value 所構成的群集,其中的值可使用特性來存取。
- 特性具有特徵,可以透過特性描述器來控制。使用對應的方法來控制物件的可變性。
- 可以使用 for..of 來迭代陣列或物件中的值,它會尋找迭代器物件,一次提取一個值出來。