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..inObject.keys(..) 等列舉動作時,該特性將不會出現。

不可變性

  • 物件常數:使用 writable: falseconfigurable: 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]]

演算法會大致檢查:

  1. 該特性是一個存取器描述器嗎?若是,就呼叫 Setter。
  2. 該特性是 writable 為 false 的資料描述器嗎?若是,就會設值失敗。
  3. 否則,按照正常方式將值設定給特性。

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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *