使用Object.observe 实现数据绑定
Object.observe API概述
最近,JavaScript的MVC框架在Web開(kāi)發(fā)屆非常盛行。在實(shí)現(xiàn)MVC框架的時(shí)候,一個(gè)非常重要的技術(shù)就是數(shù)據(jù)綁定技術(shù)。如果要實(shí)現(xiàn)模型與視圖的分離,就必須要使用數(shù)據(jù)綁定技術(shù)。但是,MVC框架的原作者對(duì)于數(shù)據(jù)綁定處理實(shí)現(xiàn)得并不如人意,因此,Google公司在ECMAScript中封裝了一個(gè)Object.observe API,專用于實(shí)現(xiàn)數(shù)據(jù)綁定處理(目前將其正式使用在V8中)。
Object.observe API可以被稱為一種“可以對(duì)任何對(duì)象的屬性值修改進(jìn)行監(jiān)視的事件處理函數(shù)”。
在Firefox瀏覽器中,實(shí)現(xiàn)了與之相類似的可以對(duì)DOM對(duì)象進(jìn)行觀察的Mutation觀察器。
目前為止,Object.observe API已經(jīng)被strawman proposal所承認(rèn),被正式使用在V8中。自11月末開(kāi)始,已經(jīng)可以在Chrome Canary與開(kāi)發(fā)者通道中對(duì)其進(jìn)行啟用。
本文介紹Object.observe API中的基本功能及一些代碼示例。
目前為止,Object.observe API中包括如下所示的四個(gè)方法:
- Object.observe:為對(duì)象指定監(jiān)視時(shí)調(diào)用的回調(diào)函數(shù)
- Object.unobserve:移除監(jiān)視時(shí)調(diào)用的回調(diào)函數(shù)
- Object.deliverChangeRecords:通過(guò)回調(diào)函數(shù)對(duì)對(duì)象值進(jìn)行修改
- Object.getNotifier:獲取Notifier對(duì)象
可以觀察到的屬性操作包括以下幾種:
- new:添加屬性
- updated:修改屬性值
- reconfigured:修改屬性設(shè)定
- deleted:刪除屬性
接下來(lái)介紹如何使用Object.observe方法。
目前(2012年12月6日)為止,如果要使用Object.observe API,需要使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,同時(shí)在chrome://flags/中啟用“啟用實(shí)驗(yàn)性 JavaScript”選項(xiàng),如下圖所示。
簡(jiǎn)單代碼示例
Object.observe方法用于為對(duì)象指定監(jiān)視到屬性修改時(shí)調(diào)用的回調(diào)函數(shù),使用方法如下所示。
Object.observe(obj, callback);Object.observe方法中使用兩個(gè)參數(shù),其中第一個(gè)參數(shù)值為需要被監(jiān)視的對(duì)象,第二個(gè)參數(shù)值為監(jiān)視到屬性修改時(shí)調(diào)用的回調(diào)函數(shù)名。可以將各對(duì)象的屬性操作時(shí)生成的ChangeRecord對(duì)象數(shù)組設(shè)置為回調(diào)函數(shù)的參數(shù)。
ChangeRecord對(duì)象擁有type、name、oldValue、object四個(gè)屬性,各屬性含義如下所示。
function callback(changes) {changes.forEach(function(change) {console.log(change.type); //對(duì)屬性進(jìn)行了什么操作 new/updated/reconfigured/deltedconsole.log(change.name); //屬性名console.log(change.oldValue); //修改之前的屬性值console.log(change.object); //被監(jiān)視的對(duì)象}); }使用如下所示的代碼,可以在任何時(shí)刻對(duì)于對(duì)象屬性的上述四種操作(new/updated/reconfigured/delted)進(jìn)行監(jiān)視:
var obj = {a: 1};Object.observe(obj, output); //為對(duì)象指定監(jiān)視時(shí)調(diào)用的回調(diào)函數(shù)obj.b = 2; //添加屬性obj.a = 2; //修改屬性值Object.defineProperties(obj, {a: { enumerable: false}}); //修改屬性設(shè)定delete obj.b; //刪除屬性 function output(change) {//回調(diào)函數(shù),可以在此處書(shū)寫在頁(yè)面上的輸出。} <!DOCTYPE html> <head> <meta charset="UTF-8" /> <title>Object.observer API代碼示例頁(yè)面</title> <style> table, td, th { border: 2px #000000 solid; } </style> <script> window.addEventListener('DOMContentLoaded',function() { if (!Object.observe) { alert('您的瀏覽器不支持Object.observe API。請(qǐng)使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,并啟用“啟用實(shí)驗(yàn)性 JavaScript”選項(xiàng)。'); return; } var obj = {a: 1}; Object.observe(obj, output); obj.b = 2; //添加屬性 obj.a = 2; //修改屬性值 Object.defineProperties(obj, {a: { enumerable: false}}); //修改屬性設(shè)定 delete obj.b; //刪除屬性 function output(changes) { var results = document.getElementById('results'); var table = document.createElement('table'); results.appendChild(table); var caption = document.createElement('caption'); caption.innerText = '監(jiān)視到的事件列表'; table.appendChild(caption); var thead = document.createElement('thead'); thead.innerHTML = '<tr><th>序號(hào)</th><th>操作種類</th><th>屬性名</th><th>修改前的屬性值</th><th>修改后的屬性值</th></tr>'; table.appendChild(thead); changes.forEach(function(change, i) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>'; table.appendChild(tr); }); } }); </script> </head> <body> <div id="event"><div>示例代碼</div> <pre> obj.b = 2; //添加屬性 obj.a = 2; //修改屬性值 Object.defineProperties(obj, {a: { enumerable: false}}); //修改屬性設(shè)定 delete obj.b; //刪除屬性 </pre> </div> <div id="results"></div> </body> </html>運(yùn)行代碼
頁(yè)面運(yùn)行結(jié)果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)
創(chuàng)建自定義Notify對(duì)象
可以監(jiān)視到的事件并不局限于以上所述的幾種,可以自定義監(jiān)視事件。
可以使用Notifier對(duì)象來(lái)自定義針對(duì)對(duì)象的可訪問(wèn)屬性(可使用getter方法或setter方法讀取或設(shè)置的屬性)被修改時(shí)所觸發(fā)的事件。這時(shí),我們需要Object.getNotifier()方法獲取被監(jiān)視對(duì)象的Notifier對(duì)象,并使用notify方法進(jìn)行屬性被修改的通知。
在以下這個(gè)示例代碼中,為對(duì)象自定義time_updated事件及time_read事件并利用這兩個(gè)事件監(jiān)視對(duì)象的私有屬性_time的讀取及修改。
var obj2 = {_time: new Date(0)}; var notifier = Object.getNotifier(obj2); //獲取Notifier對(duì)象 Object.defineProperties(obj2, { //設(shè)置對(duì)象的可訪問(wèn)屬性 _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); //為對(duì)象指定監(jiān)視時(shí)調(diào)用的回調(diào)函數(shù) //執(zhí)行屬性操作 var first_time = obj2.seen; //觸發(fā)time_read事件 obj2.seen = new Date(); //觸發(fā)time_updated事件 var second_time = obj2.seen; //觸發(fā)time_read事件 <!DOCTYPE html> <head> <title>Object.observer API代碼示例頁(yè)面</title> <style> table, td, th { border: 2px #000000 solid; } </style> <script> window.addEventListener('DOMContentLoaded',function() { if (!Object.observe) { alert('您的瀏覽器不支持Object.observe API。請(qǐng)使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,并啟用“啟用實(shí)驗(yàn)性 JavaScript”選項(xiàng)。'); return; } var obj2 = {_time: new Date(0)}; var notifier = Object.getNotifier(obj2); 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 seen = document.getElementById('seen'); var first_seen = document.createElement('div'); var first_time = obj2.seen; //觸發(fā)time_read事件 first_seen.innerText = 'first_seen:' + first_time; seen.appendChild(first_seen); obj2.seen = new Date(); //觸發(fā)time_updated事件 var second_seen = document.createElement('div'); var second_time = obj2.seen; //觸發(fā)time_read事件 second_seen.innerText = 'second_seen:' + second_time; seen.appendChild(second_seen); function output(changes) { var results = document.getElementById('results'); var table = document.createElement('table'); results.appendChild(table); var caption = document.createElement('caption'); caption.innerText = '監(jiān)視到的事件列表'; table.appendChild(caption); var thead = document.createElement('thead'); thead.innerHTML = '<tr><th>序號(hào)</th><th>操作種類</th><th>屬性名</th><th>修改前的屬性值</th></tr>'; table.appendChild(thead); changes.forEach(function(change, i) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td>'; table.appendChild(tr); }); } }); </script> </head> <body> <div id="event"><div>示例代碼</div> <pre> var first_time = obj2.seen; //觸發(fā)time_read事件 obj2.seen = new Date(); //觸發(fā)time_updated事件 var second_time = obj2.seen; //觸發(fā)time_read事件 </pre> </div> <div id="seen"></div> <div id="results"></div> </body> </html>運(yùn)行代碼
控制回調(diào)函數(shù)的執(zhí)行時(shí)間
在默認(rèn)情況下,使用Object.observe API指定的回調(diào)函數(shù)將在JavaScript腳本代碼執(zhí)行結(jié)束時(shí)被調(diào)用。因此如果對(duì)同一對(duì)象的同一屬性執(zhí)行了多次操作,回調(diào)函數(shù)中獲取到的各屬性值為最后一個(gè)操作結(jié)束后的值。將前面這個(gè)示例中的代碼稍作修改,對(duì)使用Object.observe API進(jìn)行監(jiān)視的對(duì)象的屬性值連續(xù)修改七次(為了避免回調(diào)函數(shù)的循環(huán)調(diào)用刪除對(duì)time_read事件的監(jiān)視)。
obj3.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件obj3.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 <!DOCTYPE html> <head> <title>Object.observer API代碼示例頁(yè)面</title> <style> table, td, th { border: 2px #000000 solid; } </style> <script> window.addEventListener('DOMContentLoaded',function() { if (!Object.observe) { alert('您的瀏覽器不支持Object.observe API。請(qǐng)使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,并啟用“啟用實(shí)驗(yàn)性 JavaScript”選項(xiàng)。'); return; } var obj3 = {_time: new Date(0)}; var notifier = Object.getNotifier(obj3); Object.defineProperties(obj3, { _time: { enumerable: false, configrable: false }, seen: { set: function(val) { var notifier = Object.getNotifier(this); notifier.notify({ type: 'time_updated', // 時(shí)間更新イベントの定義 name: 'seen', oldValue: this._time }); this._time = val; }, get: function() { return this._time; } } }); Object.observe(obj3, output); obj3.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 function output (changes) { var results = document.getElementById('results'); var table = document.createElement('table'); results.appendChild(table); var caption = document.createElement('caption'); caption.innerText = '監(jiān)視到的事件列表'; table.appendChild(caption); var thead = document.createElement('thead'); thead.innerHTML = '<tr><th>序號(hào)</th><th>操作種類</th><th>屬性名</th><th>修改前的屬性值</th><th>修改后的屬性值</th></tr>'; table.appendChild(thead); changes.forEach(function(change, i) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>'; table.appendChild(tr); }); } }); </script> </head> <body> <div id="event"><div>示例代碼</div> <pre> obj3.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件 obj3.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 </pre> </div> <div id="seen"></div> <div id="results"></div> </body> </html>運(yùn)行代碼
頁(yè)面運(yùn)行結(jié)果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)
從這個(gè)結(jié)果中我們可以看出,在回調(diào)函數(shù)中獲取到的屬性值為同一個(gè)屬性值(2013年1月7日)。為了強(qiáng)制獲取事件觸發(fā)后立即設(shè)置的屬性值,我們需要使用Object.deliverChangeRecords方法。
在如下所示的代碼中,每次修改了屬性值后,即調(diào)用Object.deliverChangeRecords方法立即調(diào)用回調(diào)函數(shù)。
obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù)obj4.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 <!DOCTYPE html> <head> <title>Object.observer API代碼示例頁(yè)面</title> <style> table, td, th { border: 2px #000000 solid; } </style> <script> window.addEventListener('DOMContentLoaded',function() { if (!Object.observe) { alert('您的瀏覽器不支持Object.observe API。請(qǐng)使用Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器,并啟用“啟用實(shí)驗(yàn)性 JavaScript”選項(xiàng)。'); return; } var obj4 = {_time: new Date(0)}; var notifier = Object.getNotifier(obj4); Object.defineProperties(obj4, { _time: { enumerable: false, configrable: false }, seen: { set: function(val) { var notifier = Object.getNotifier(this); notifier.notify({ type: 'time_updated', // 時(shí)間更新イベントの定義 name: 'seen', oldValue: this._time }); this._time = val; }, get: function() { return this._time; } } }); Object.observe(obj4, output); obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 function output (changes) { var results = document.getElementById('results'); var table = document.createElement('table'); results.appendChild(table); var caption = document.createElement('caption'); caption.innerText = '監(jiān)視到的事件列表'; table.appendChild(caption); var thead = document.createElement('thead'); thead.innerHTML = '<tr><th>序號(hào)</th><th>操作種類</th><th>屬性名</th><th>修改前的屬性值</th><th>修改后的屬性值</th></tr>'; table.appendChild(thead); changes.forEach(function(change, i) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + i + '</td><td>' + change.type + '</td><td>' + change.name + '</td><td>' + change.oldValue + '</td><td>' + change.object[change.name] + '</td>'; table.appendChild(tr); }); } }); </script> </head> <body> <div id="event"><div>示例代碼</div> <pre> obj4.seen = new Date(2013, 0, 1, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 2, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 3, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 4, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 5, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 6, 0, 0, 0); //觸發(fā)time_updated事件 Object.deliverChangeRecords(output); //調(diào)用回調(diào)函數(shù) obj4.seen = new Date(2013, 0, 7, 0, 0, 0); //觸發(fā)time_updated事件 </pre> </div> <div id="seen"></div> <div id="results"></div> </body> </html>運(yùn)行代碼
頁(yè)面運(yùn)行結(jié)果如下圖所示(在Chrome Canary或Chrome Dev Channel(25.0.1337.0 dev-m以上)版本的瀏覽器中)
從這個(gè)結(jié)果中我們可以看出,每次執(zhí)行Object.deliverChangeRecords方法時(shí)都將調(diào)用回調(diào)函數(shù)在頁(yè)面中輸出修改后的屬性值。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的使用Object.observe 实现数据绑定的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 最新全球排名前50网站前端开发语言统计
- 下一篇: c#中的弱引用:WeakReferenc