位元詩人 現代 [JavaScript] 程式設計教學:使用物件實字 (Object Literal)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在 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,之後建立兩個屬性 xy

當我們建立物件的方法 (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) 的工具函式 copycopy 函式的原理相當簡單,就是建立一個新的物件實字 dest,將所有的屬性從 src 逐一拷貝過去即可。

相對來說,xy 在每個物件各自有一份,不會改某個物件的屬性而影響到另一個物件的屬性。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。