前言
在 JavaScript 中,物件實字 {}
是以鍵值對來儲存資料的物件,有以下用途:
- 模擬映射 (map)
- 模擬命名空間 (namespace)
- 撰寫基於物件 (object-based) 的程式
在 ES6 加入新的語法特性後,除了模擬命名空間這個任務外,其他的用途都有更好的方式可以替代,不再建議使用物件實字。但現有的 ES5 代碼大量使用物件實字,JSON 也會用到物件實字,故仍要熟悉這項特性。
把物件實字當成映射
以下的範例把物件實字當成映射使用:
// Home-made `assert`.
function assert(cond, msg) {
if (!(cond)) {
throw (msg ? msg : 'Assertion failed');
}
}
// Create an object literal as a map.
let map = {};
// Add some key-value pairs.
map.one = "eins";
map.two = "zwei";
map.three = "drei";
// Check whether the pair exists.
assert(map.two === "zwei", "map.two should be zwei");
// Delete a key-value pair.
delete map.two;
// Recheck its existence.
assert(typeof map.two === "undefined", "map.two should not exist");
在這個簡短的例子中,可以看到映射的各種情境,包括建立物件、新增鍵值對、確認鍵值對的值、移除鍵值對等。
用物件實字模擬命名空間
以下是一個假想的例子:
// Create the namespace `come.example`.
var com = com || {};
com.example = com.example || {};
// Create the variable `foo` in `com.example` namespace.
com.example.foo = "Some string";
// Create the function `bar` in `com.example` namespace.
com.example.bar = function () {
// Implement your code here.
};
在這個例字中,我們用兩層物件實字建立新的命名空間 com.example
,就可以在該命名空間下放入資料、函式等。
在網頁前端程式中,模組是後來才加入的概念。在預設情形下,所有的函式庫都會自動進入全域命名空間中。所以,利用物件實字模擬命名空間是重要的手法。
用物件實字寫物件
以下是一個略長的例子,請讀者先試著讀一下,我們會講解。
/* Deep copy. */
function copy (src) {
let dest = {};
// Inherit prototype from `src`.
Object.setPrototypeOf(dest, Object.getPrototypeOf(src));
// Copy properties from `src`.
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
dest[prop] = src[prop];
}
}
return dest;
}
/* Home-made `assert`. */
function assert(cond, msg) {
if (!(cond)) {
throw (msg ? msg : 'Assertion failed');
}
}
// Create the object `Point`.
let Point = {};
// Fields per object.
Point.x = 0;
Point.y = 0;
// Function shared among objects.
Object.setPrototypeOf(Point, {
distance: function (p, q) {
let dx = p.x - q.x;
let dy = p.y - q.y;
return Math.sqrt(dx * dx + dy * dy);
}
});
let p = copy(Point);
let q = copy(Point);
q.x = 3;
q.y = 4;
assert(Point.distance(p, q) === 5, "The distance should be 5");
我們一開始建立物件 Point
,之後建立兩個屬性 x
和 y
。
當我們建立物件的方法 (method) distance
時,我們沒有將 distance
函式直接建立在物件 Point
上,而是建立在 Point
的原型鏈 (prototype) 上。這是為了簡約記憶體。有些教材會這樣寫:
Point.distance = function (p, q) {
let dx = p.x - q.x;
let dy = p.y - q.y;
return Math.sqrt(dx * dx + dy * dy);
};
這樣寫的話,函式 distance
視為物件 Point
的屬性。每次拷貝 Point
時,都會拷貝一份函式 distance
。將共用的函式寫到原型鏈上,可以節約記憶體。我們會在後文講到原型鏈。
JavaScript 沒有類別 (class) 的概念,想要做新物件時,從原物件拷貝一個即可。這是因為 JavaScript 的物件是基於原型 (self-based)。
內建的物件拷貝函式是 Object.assign()。但 Object.assign()
是淺拷貝 (shallow copy),故我們自己寫了一個深拷貝 (deep copy) 的工具函式 copy
。copy
函式的原理相當簡單,就是建立一個新的物件實字 dest
,將所有的屬性從 src
逐一拷貝過去即可。
相對來說,x
和 y
在每個物件各自有一份,不會改某個物件的屬性而影響到另一個物件的屬性。