你所不知道的 JavaScript Object

以前在寫 VB 時,看到物件有 set/get 可以用,你可以用簡單的存取方式設定或取得物件的某個屬性,但是實際上存取時做一些操作和運算,對物件外來說,就和一般屬性一樣的,當時覺得這個特性還蠻方便的,不過這不是所有程式語言都有的,像是當年的 JavaScript 就沒有,不過時光飛逝,歲月如梭,其實現在 JavaScript 也支援 get/set 了,只是要用 defineProperty 來設定,沒辦法用 Good Part 裡面建議的,單純用 object literal notation,當然比較舊的 IE 也不支援,寫起來如下:

var square = {
    side: 1
};

Object.defineProperty(square, 'area', {
    get: function () {
        return Math.pow(this.side, 2);
    },
    set: function (val) {
        this.side = Math.pow(val, 0.5);
    }
});

不過,defineProperty 的功能可不止於此,它提供了更改更為底層設定的能力,包括 configurable、enumerable 和 writable 三個設定。configurable 表示這個屬性的設定是否可以再次用 defineProperty 更改,writable 比較簡單就是表示這個屬性的值是否可以修改,enumerable 則是設定這個屬性在列舉的時候會不會被列出,物件的列舉其實就是用for in

for (attr in obj) {
    obj[attr] = 'foo';
}

如果有在用 JSLint 可能會看過這樣寫的話都會跳出下面的錯誤訊息:

The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype

這是因為用for in的話,會把 prototype 裡面的屬性也列舉出來,這樣會有什麼問題呢,以前在做陣列的新 method 的 polyfill 時,會有個狀況就是,我們手動新增的 method,會在列舉時被列出,所以變成所有的陣列都不能用for in來跑,但是現在有 defneProperty 的 enumerable 了,未來在有類似的狀況,可以直接給陣列新增 method 而不會影響for in的行為。

除了 defineProperty 之外,還有 defineProperties 可以大量設定屬性,另外也可以在 Object.create 時直接設定好。

第二個要說的是,現在 JavaScript 的物件可以 (seal) 起來或是 (freeze) 起來了,seal 做的事情是限制物件屬性的新增或刪除,既有屬性的值還是可以修改,freeze 則是把物件完全凍結住,連修改屬性都不可以。而且這些行為是不可逆的,沒有提供可以 unseal 或是 unfreeze 的方法,不過倒是有isSealedisFrozen可以判斷物件狀態。

在 strict mode 下,上面兩個特性有限制的地方在被違反的時候,都會 throw error,普通模式下是不會有錯誤,不過他的行為還是不變,使用時需要注意。

最後要介紹的是還不能使用的 observe,observe 又稱為觀察者,在 Backbone 有利用 set 和事件系統來實做,是在開發應用程式時很有用的 pattern,TC39 有打算把它加進 JavaScript 之中,目前還只有草案就是,要能用應該還要好一陣子就是了。