語法
宣告式 (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 來迭代陣列或物件中的值,它會尋找迭代器物件,一次提取一個值出來。