Object.observe API概述
最近,JavaScript的MVC框架在Web開發屆非常盛行。在實現MVC框架的時候,一個非常重要的技術就是數據綁定技術。如果要實現模型與視圖的分離,就必須要使用數據綁定技術。但是,MVC框架的原作者對於數據綁定處理實現得並不如人意,因此,Google公司在ECMAScript中封裝了一個Object.observe API,專用於實現數據綁定處理(目前將其正式使用在V8中)。
Object.observe API可以被稱為一種“可以對任何對象的屬性值修改進行監視的事件處理函數”。
在Firefox瀏覽器中,實現了與之相類似的可以對DOM對象進行觀察的Mutation觀察器。
目前為止,Object.observe API已經被strawman proposal所承認,被正式使用在V8中。自11月末開始,已經可以在Chrome Canary與開發者通道中對其進行啟用。
本文介紹Object.observe API中的基本功能及一些代碼示例。
目前為止,Object.observe API中包括如下所示的四個方法:
- Object.observe:為對象指定監視時調用的回調函數
- Object.unobserve:移除監視時調用的回調函數
- Object.deliverChangeRecords:通過回調函數對對象值進行修改
- Object.getNotifier:獲取Notifier對象
可以觀察到的屬性操作包括以下幾種:
- new:添加屬性
- updated:修改屬性值
- reconfigured:修改屬性設定
- deleted:刪除屬性
接下來介紹如何使用Object.observe方法。
目前(2012年12月6日)為止,如果要使用Object.observe API,需要使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,同時在chrome://flags/中啟用“啟用實驗性 JavaScript”選項,如下圖所示。

簡單代碼示例
Object.observe方法用於為對象指定監視到屬性修改時調用的回調函數,使用方法如下所示。
Object.observe(obj, callback);
Object.observe方法中使用兩個參數,其中第一個參數值為需要被監視的對象,第二個參數值為監視到屬性修改時調用的回調函數名。可以將各對象的屬性操作時生成的ChangeRecord對象數組設置為回調函數的參數。
ChangeRecord對象擁有type、name、oldValue、object四個屬性,各屬性含義如下所示。
function callback(changes) { changes.forEach(function(change) { console.log(change.type); //對屬性進行了什么操作 new/updated/reconfigured/delted console.log(change.name); //屬性名 console.log(change.oldValue); //修改之前的屬性值 console.log(change.object); //被監視的對象 }); }
使用如下所示的代碼,可以在任何時刻對於對象屬性的上述四種操作(new/updated/reconfigured/delted)進行監視:
var obj = {a: 1}; Object.observe(obj, output); //為對象指定監視時調用的回調函數 obj.b = 2; //添加屬性 obj.a = 2; //修改屬性值 Object.defineProperties(obj, {a: { enumerable: false}}); //修改屬性設定 delete obj.b; //刪除屬性 function output(change) { //回調函數,可以在此處書寫在頁面上的輸出。 }
頁面運行結果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)

創建自定義Notify對象
可以監視到的事件並不局限於以上所述的幾種,可以自定義監視事件。
可以使用Notifier對象來自定義針對對象的可訪問屬性(可使用getter方法或setter方法讀取或設置的屬性)被修改時所觸發的事件。這時,我們需要Object.getNotifier()方法獲取被監視對象的Notifier對象,並使用notify方法進行屬性被修改的通知。
在以下這個示例代碼中,為對象自定義time_updated事件及time_read事件並利用這兩個事件監視對象的私有屬性_time的讀取及修改。
var obj2 = {_time: new Date(0)}; var notifier = Object.getNotifier(obj2); //獲取Notifier對象 Object.defineProperties(obj2, { //設置對象的可訪問屬性 _time: { enumerable: false, configrable: false }, seen: { set: function(val) { var notifier = Object.getNotifier(this); notifier.notify({ type: 'time_updated', //定義time_updated事件 name: 'seen', oldValue: this._time }); this._time = val; }, get: function() { var notifier = Object.getNotifier(this); notifier.notify({ type: 'time_read', //定義time_read事件 name: 'seen', oldValue: this._time }); return this._time; } } }); Object.observe(obj2, output); //為對象指定監視時調用的回調函數 //執行屬性操作 var first_time = obj2.seen; //觸發time_read事件 obj2.seen = new Date(); //觸發time_updated事件 var second_time = obj2.seen; //觸發time_read事件

控制回調函數的執行時間
在默認情況下,使用Object.observe API指定的回調函數將在JavaScript腳本代碼執行結束時被調用。因此如果對同一對象的同一屬性執行了多次操作,回調函數中獲取到的各屬性值為最后一個操作結束后的值。將前面這個示例中的代碼稍作修改,對使用Object.observe API進行監視的對象的屬性值連續修改七次(為了避免回調函數的循環調用刪除對time_read事件的監視)。
obj3.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發time_updated事件 obj3.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發time_updated事件
頁面運行結果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)

從這個結果中我們可以看出,在回調函數中獲取到的屬性值為同一個屬性值(2013年1月7日)。為了強制獲取事件觸發后立即設置的屬性值,我們需要使用Object.deliverChangeRecords方法。
在如下所示的代碼中,每次修改了屬性值后,即調用Object.deliverChangeRecords方法立即調用回調函數。
obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發time_updated事件 Object.deliverChangeRecords(output); //調用回調函數 obj4.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發time_updated事件
頁面運行結果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)

從這個結果中我們可以看出,每次執行Object.deliverChangeRecords方法時都將調用回調函數在頁面中輸出修改后的屬性值。