現代 JavaScript 程式設計教學:使用物件實字 (Object Literal)

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    在 JavaScript 中,物件實字 {} 有以下用途:

    • 當成映射 (map)
    • 模擬命名空間 (namespace)
    • 撰寫基於物件 (object-based) 的程式

    在 ES6 之後,除了模擬命名空間這個任務外,其他的用途都有更好的方式可以替代,不再建議使用物件實字。但現有的 ES5 代碼大量使用物件實字,JSON 也會用到物件實字,故仍要熟悉這項特性。

    把物件實字當成映射

    以下的例子把物件實字當成映射使用:

    // 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");
    
    // Home-made `assert`.
    function assert(cond, msg) {
        if (!(cond)) {
            throw msg;
        }
    }
    

    在這個簡短的例子中,可以看到映射的各種情境,包括建立物件、新增鍵值對、確認鍵值對的值、移除鍵值對等。

    用物件實字模擬命名空間

    以下是一個假想的例子:

    // 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,就可以在該命名空間下放入資料、函式等。

    用物件實字寫物件

    以下是一個略長的例子,請讀者先試著讀一下,我們會講解。

    // 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");
    
    // 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;
        }
    }
    

    我們一開始建立物件 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 在每個物件各自有一份,不會改某個物件的屬性而影響到另一個物件的屬性。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk