一場(chǎng)變革即將到來(lái)。Javascript中的一項(xiàng)新特性將會(huì)顛覆之前你對(duì)于數(shù)據(jù)綁定的所有認(rèn)識(shí)。它也將改變你所使用的MVC庫(kù)觀察模型中發(fā)生的修改以及更新的實(shí)現(xiàn)方式。你會(huì)看到,那些所有在意屬性觀察的應(yīng)用性能將會(huì)得到巨大的提升。
我們很高興的看到,Object.observe()
已經(jīng)正式加入到了Chrome 36 beta版本中。
Object.observe()
是未來(lái)ECMAScript標(biāo)準(zhǔn)之一,它是一個(gè)可以異步觀察Javascript中對(duì)象變化的方法,而無(wú)需使用一個(gè)其他的JS庫(kù)。它允許一個(gè)觀察者接收一個(gè)按照時(shí)間排序的變化記錄序列,這個(gè)序列描述的是一列被觀察的對(duì)象所發(fā)生的變化。
// Let's say we have a model with data
var model = {};
// Which we then observe
Object.observe(model, function(changes){
// This asynchronous callback runs
changes.forEach(function(change) {
// Letting us know what changed
console.log(change.type, change.name, change.oldValue);
});
});
當(dāng)被觀察的對(duì)象發(fā)生任何變化時(shí),回調(diào)函數(shù)將會(huì)匯報(bào)這些變化:
通過(guò)使用Object.observe()
,你可以不需要使用任何框架就能實(shí)現(xiàn)雙向數(shù)據(jù)綁定。
但這并不意味著你就不應(yīng)該使用一個(gè)框架。對(duì)于一些有著復(fù)雜業(yè)務(wù)邏輯的項(xiàng)目,經(jīng)過(guò)精心設(shè)計(jì)的框架的重要性不言而喻,你應(yīng)該繼續(xù)使用它們。這些框架減輕了開發(fā)新手的工作量,而且只需要編寫很少的代碼就能夠維護(hù)和實(shí)現(xiàn)一些模式并達(dá)到我們想要的目的。如果你不需要一個(gè)框架,你可以使用一個(gè)體積更小,針對(duì)性更強(qiáng)的庫(kù),比如Polymer(它已經(jīng)開始使用Object.observe()
了)。
即便你已經(jīng)重度依賴于一個(gè)MV*
框架,Object.observe()
仍然能為你的應(yīng)用帶來(lái)一些性能各方面的提升,它能夠更快更簡(jiǎn)單的實(shí)現(xiàn)一些功能并維持同樣的API。例如,在Angular以前的一個(gè)Benchmark測(cè)試中,對(duì)于一個(gè)Model中發(fā)生的變化,臟值檢查對(duì)每次更新會(huì)花費(fèi)40ms,而Object.observe()
只會(huì)花費(fèi)1-2ms(相當(dāng)于20-40倍的性能提升)。
不需要冗長(zhǎng)代碼來(lái)實(shí)現(xiàn)的數(shù)據(jù)雙向綁定還意味著你不需要通過(guò)輪詢來(lái)發(fā)現(xiàn)變化,這將帶來(lái)更長(zhǎng)的電池使用時(shí)間!
如果你已經(jīng)對(duì)Object.observe()
有了一些了解,可以直接跳過(guò)簡(jiǎn)介這一節(jié),或者接著閱讀了解更多關(guān)于它能夠解決的問(wèn)題。
當(dāng)我們?cè)谟懻摂?shù)據(jù)觀察時(shí),我們通常指的是對(duì)一些特定類型的數(shù)據(jù)變化保持關(guān)注:
當(dāng)你開始關(guān)心模型-視圖的控制分離時(shí),數(shù)據(jù)綁定就會(huì)變成一件重要的事。HTML是一個(gè)非常好的聲明機(jī)制,但是它完全是靜態(tài)的。理想狀態(tài)下,你想要在數(shù)據(jù)和DOM之間聲明它們的關(guān)系,以便讓DOM保持更新。這會(huì)讓你節(jié)省很多由于寫一些重復(fù)代碼而浪費(fèi)的時(shí)間。
當(dāng)你擁有一個(gè)復(fù)雜的用戶界面,你需要理清楚許多數(shù)據(jù)屬性和許多視圖中元素的關(guān)系時(shí),數(shù)據(jù)綁定是非常有用的。這在我們今天需要?jiǎng)?chuàng)建的單頁(yè)應(yīng)用中非常常見。
通過(guò)在瀏覽器中原生的觀察數(shù)據(jù),我們給予了JavaScript框架(或者你編寫的一些功能庫(kù))一種方式來(lái)實(shí)現(xiàn)對(duì)模型數(shù)據(jù)的變化進(jìn)行觀察而不需要依賴于我們今天正在使用的一些hack方法。
你以前曾經(jīng)在那里看到過(guò)數(shù)據(jù)綁定?如果你在你的web應(yīng)用中使用過(guò)一個(gè)現(xiàn)代MV*框架(例如Angular,Knockout),那么你或許已經(jīng)使用過(guò)數(shù)據(jù)綁定將數(shù)據(jù)綁定到你的DOM上了。為了復(fù)習(xí)一下,下面是一個(gè)電話列表應(yīng)用的例子,在其中我們會(huì)將一個(gè)phones數(shù)組中的值(在JavaScript中定義)綁定到一個(gè)列表項(xiàng)目中以便于我們的數(shù)據(jù)和UI保持同步。
<html ng-app>
<head>
...
<script src="angular.js"></script>
<script src="controller.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<ul>
<li ng-repeat="phone in phones">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>
js的controller如下:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function($scope) {
$scope.phones = [
{
'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'
}, {
'name': 'Motorola XOOM with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'
}, {
'name': 'MOTOROLA XOOM',
'snippet': 'The Next, Next Generation tablet.'
}
];
});
demo的地址在這里。
在任何時(shí)候,只要是底層的model數(shù)據(jù)發(fā)生了變化,我們?cè)贒OM中的列表也會(huì)跟著更新。Angular是怎么做到這一點(diǎn)的呢?在Angular的背后,有一個(gè)叫做臟值檢查的東西。
臟值檢查的基本原理就是只要任何時(shí)候數(shù)據(jù)發(fā)生了變化,這個(gè)庫(kù)都會(huì)通過(guò)一個(gè)digest
或者change cycle
去檢查變化是否發(fā)生了。在Angular中,一個(gè)digest
循環(huán)意味著所有被監(jiān)視的表達(dá)式都會(huì)被循環(huán)一遍以便查看其中是否有變化發(fā)生。它知道一個(gè)模型之前的值,因此當(dāng)變化發(fā)生時(shí)一個(gè)change事件將會(huì)被觸發(fā)。對(duì)于開發(fā)者來(lái)說(shuō),這帶來(lái)的一大好處就是你可以使用原生的JavaScript對(duì)象數(shù)據(jù),它易于使用及整合。下面的圖片展示的是一個(gè)非常糟糕的算法,它的開銷非常大。
這個(gè)操作的開銷和被監(jiān)視的對(duì)象的數(shù)量是成正比的。我們可能需要做很多的臟治檢查。同時(shí)我也需要一種方式去觸發(fā)臟值檢查,當(dāng)某些數(shù)據(jù)可能發(fā)生改變時(shí)。有很多的框架使用了一些非常聰明的方法來(lái)解決這個(gè)問(wèn)題,但是它們是否足夠好目前還尚無(wú)定論。
web生態(tài)系統(tǒng)應(yīng)該擁有更多的能力去創(chuàng)新和進(jìn)化它自己的聲明機(jī)制,例如:
容器對(duì)象是一個(gè)框架創(chuàng)建的對(duì)象,它能夠在其中保存一些數(shù)據(jù)。它們擁有一些存取器去獲取數(shù)據(jù)并且能夠在你設(shè)置或者獲取對(duì)象時(shí)捕獲到這些行為并在內(nèi)部進(jìn)行廣播。這是一種非常好的方式。它的性能很好,從算法上來(lái)說(shuō)也不錯(cuò)。下面是一個(gè)使用Ember容器對(duì)象的一個(gè)簡(jiǎn)單例子:
// Container objects
MyApp.president = Ember.Object.create({
name: "Barack Obama"
});
MyApp.country = Ember.Object.create({
// ending a property with "Binding" tells Ember to
// create a binding to the presidentName property
presidentNameBinding: "MyApp.president.name"
});
// Later, after Ember has resolved bindings
MyApp.country.get("presidentName");
// "Barack Obama"
// Data from the server needs to be converted
// Composes poorly with existing code
在上面的例子中,發(fā)現(xiàn)什么地方發(fā)生了變化的開銷和發(fā)生改變的東西有著直接聯(lián)系?,F(xiàn)在你存在的另一個(gè)問(wèn)題是你需要使用不同種類的對(duì)象??偟膩?lái)說(shuō)你需要將從服務(wù)器獲取的數(shù)據(jù)進(jìn)行轉(zhuǎn)換以便它們是能夠被觀察到的。
目前的JS代碼并不能很好的整合生成數(shù)據(jù),因?yàn)檫@些代碼一般會(huì)假設(shè)它們操作的是原生JavaScript對(duì)象,而不是一些特定的對(duì)象類似類型。
我們真正想要的可能是兩個(gè)世界中最好的東西 – 一種支持對(duì)原生數(shù)據(jù)對(duì)象(普通JavaScript對(duì)象)進(jìn)行觀察的方法,同時(shí)不需要每次都對(duì)所有東西進(jìn)行臟值檢查。它需要有良好的算法表現(xiàn)。它還需要能夠很好的整合到各個(gè)平臺(tái)中。這些都是Object.observe()
能夠帶給我們的東西。
它允許我們對(duì)一個(gè)對(duì)象或者變異屬性進(jìn)行觀察,并且在變化發(fā)生時(shí)得到及時(shí)通知。但是我們?cè)谶@里不想看什么理論,讓我們來(lái)看看代碼!
讓我們假設(shè)我們現(xiàn)在有一個(gè)簡(jiǎn)單的JavaScript對(duì)象,它代表一個(gè)模型:
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
我們可以制定一個(gè)比回調(diào)函數(shù),用來(lái)處理對(duì)象上的變化:
function observer(changes){
changes.forEach(function(change, i){
console.log('what property changed? ' + change.name);
console.log('how did it change? ' + change.type);
console.log('whats the current value? ' + change.object[change.name]);
console.log(change); // all changes
});
}
注意:當(dāng)觀察者回調(diào)函數(shù)被調(diào)用時(shí),被觀察的對(duì)象可能已經(jīng)發(fā)生了多次改變,因此對(duì)于每一次變化,新的值(即每次變化以后的值)和當(dāng)前值(最終的值)并不一定是相同的。
我們可以使用Object.observe()
來(lái)觀察這些變化,只要將對(duì)象作為第一個(gè)參數(shù),而將回調(diào)函數(shù)作為第二個(gè)參數(shù):
Object.observe(todoModel, observer);
我們現(xiàn)在對(duì)我們的Todos的模型對(duì)象做一些改變:
todoModel.label = 'Buy some more milk';
看看控制臺(tái),我們現(xiàn)在得到了一些有用的信息!我們知道什么屬性發(fā)生了變化,它是怎樣變化的以及新的值是什么。
再見,臟值檢查!你的墓碑應(yīng)該被刻上Comic Sans字體。我們?cè)賮?lái)改變其他的屬性。這次改變的是completeBy
:
todoModel.completeBy = '01/01/2014';
正如我們所見的,我們又再一次得到了關(guān)于變化的報(bào)告:
非常好。要是我們現(xiàn)在決定從對(duì)象中刪除completed
屬性會(huì)怎么樣:
delete todoModel.completed;
正如我們所見的,返回的變化報(bào)告包含了關(guān)于刪除的信息。正如我們所期待的,新的值現(xiàn)在是undefined
。那么,我們現(xiàn)在知道了你可以知道屬性什么時(shí)候被添加。什么時(shí)候被刪除?;旧蟻?lái)說(shuō),你可以知道一個(gè)對(duì)象上的屬性集(’new’,’deleted’,’recongigured’)以及它的原型(proto)的變化。
在任何觀察系統(tǒng)中,總是存在一個(gè)方法來(lái)停止觀察。在這里,我們有Object.unobserve()
方法,它的用法和Object.observe()
一樣但是可以像下面一樣被調(diào)用:
Object.unobserve(todoModel, observer);
正如下面所示,在使用該方法之后,任何的變化都不再作為一個(gè)變化列表記錄返回。
現(xiàn)在我們已經(jīng)了解到了我們?nèi)绾稳カ@取一個(gè)被觀察對(duì)象的變化列表。但是如果我們僅僅只對(duì)一個(gè)對(duì)象中的某些屬性感興趣該怎么辦?人人都需要一個(gè)垃圾郵件過(guò)濾器。Observer
可以通過(guò)一個(gè)列表指定一些我們想要看到的變化。我們需要通過(guò)Object.observe()
的第三個(gè)參數(shù)來(lái)指定:
Object.observe(obj, callback, opt_acceptList)
現(xiàn)在我們來(lái)看一個(gè)如何使用的例子:
// Like earlier, a model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
// We then specify a callback for whenever mutations
// are made to the object
function observer(changes){
changes.forEach(function(change, i){
console.log(change);
});
};
// Which we then observe, specifying an array of change
// types we’re interested in
Object.observe(todoModel, observer, ['delete']);
// without this third option, the change types provided
// default to intrinsic types
todoModel.label = 'Buy some milk';
// note that no changes were reported
如果我們刪除了這個(gè)標(biāo)簽,注意到這個(gè)類型的變化將會(huì)被報(bào)告:
|
|
如果你不指定一個(gè)列表,它默認(rèn)將會(huì)報(bào)告固有的對(duì)象變化類型 (“add”, “update”, “delete”, “reconfigure”, “preventExtensions” (丟與那些不可擴(kuò)展的對(duì)象是不可觀察的))。
Object.observe()
也帶有一些通知。它們并不像是你在手機(jī)上看到了通知,而是更加有有用。通知和變異觀察者比較類似。它們發(fā)生在微任務(wù)的結(jié)尾。在瀏覽器的上下文,它幾乎總是位于當(dāng)前事件處理器的結(jié)尾。
這個(gè)時(shí)間點(diǎn)非常的重要因?yàn)榛旧蟻?lái)說(shuō)此時(shí)一個(gè)工作單元已經(jīng)結(jié)束了,現(xiàn)在觀察者已經(jīng)開始它們的共走了。這是一個(gè)非常好的回合處理模型。
使用一個(gè)通知器的工作流程如下所示:
現(xiàn)在我們通過(guò)一個(gè)例子來(lái)如何通過(guò)自定義一個(gè)通知器來(lái)處理一個(gè)對(duì)象的屬性被設(shè)置或者被獲取的情況。注意看代碼中的注釋:
// Define a simple model
var model = {
a: {}
};
// And a separate variable we'll be using for our model's getter in just a moment
// 定義一個(gè)單獨(dú)的變量,我們即將使用它來(lái)作為我們的模型中的getter
var _b = 2;
// Define a new property 'b' under 'a' with a custom getter and setter
// 在'a'下面定義一個(gè)新的屬性'b',并自定義一個(gè)getter和setter
Object.defineProperty(model.a, 'b', {
get: function () {
return _b;
},
set: function (b) {
// Whenever 'b' is set on the model
// notify the world about a specific type
// of change being made. This gives you a huge
// amount of control over notifications
// 當(dāng)'b'在模型中被設(shè)置時(shí),注意一個(gè)特定類型的變化將會(huì)發(fā)生
// 這將給你許多關(guān)于通知的控制器
Object.getNotifier(this).notify({
type: 'update',
name: 'b',
oldValue: _b
});
// Let's also log out the value anytime it gets
// set for kicks
// 在值發(fā)生變化時(shí)將會(huì)輸出信息
console.log('set', b);
_b = b;
}
});
// Set up our observer
// 設(shè)置我們的觀察者
function observer(changes) {
changes.forEach(function (change, i) {
console.log(change);
})
}
// Begin observing model.a for changes
Object.observe(model.a, observer);
現(xiàn)在當(dāng)數(shù)據(jù)屬性發(fā)生變化時(shí)(‘update’)我們將會(huì)得到報(bào)告。以及任何對(duì)象的實(shí)現(xiàn)也將會(huì)被報(bào)告(notifier.notifyChange()
)。
多年的web平臺(tái)開發(fā)經(jīng)驗(yàn)告訴我們整合方法是你應(yīng)該最先嘗試的事情,因?yàn)樗钊菀兹?shí)現(xiàn)。但是它存在的問(wèn)題是以它會(huì)創(chuàng)造一個(gè)從根本上來(lái)看就很未下的處理模型。如果你正在編寫代碼并且更新了一個(gè)對(duì)象的屬性,你實(shí)際上并不想陷入這樣一種困境:更新模型中的屬性會(huì)最終導(dǎo)致任意一段代碼去做任意一件事情。當(dāng)你的函數(shù)正好運(yùn)行到一半時(shí),假設(shè)失效并不是什么理想的狀況。
如果你是一個(gè)觀察者,你并不想當(dāng)某人正在做某事的時(shí)候被調(diào)用。你并不像在不連續(xù)的狀態(tài)下被調(diào)用。因?yàn)檫@最終往往會(huì)導(dǎo)致更多的錯(cuò)誤檢查。你應(yīng)該試著去容忍更多的情形,并且基本上來(lái)說(shuō)它是一個(gè)很難去合作的模型。異步是一件更難處理的事情但是最終它會(huì)產(chǎn)生更好的模型。
上述問(wèn)題的解決辦法是變化合成記錄(synthetic change records)。
基本上來(lái)說(shuō),如果你想要存取器或者計(jì)算屬性的話,你應(yīng)該復(fù)雜在這些值發(fā)生改變時(shí)發(fā)出通知。這會(huì)導(dǎo)致一些額外的工作,但是它是這種機(jī)制第一類的特征,并且這些通知會(huì)連同來(lái)自余下的底層數(shù)據(jù)對(duì)象的通知一起被發(fā)布出來(lái)。
觀察存取器或者計(jì)算屬性的問(wèn)題可以通過(guò)使用notifier.notify
來(lái)解決 – 它也是Object.observe()
的另外一部分。大多數(shù)的觀察系統(tǒng)想要某些形式的觀察導(dǎo)出值。有很多方法可以實(shí)現(xiàn)它。Object.observe()
并沒有用正確的方式進(jìn)行判斷。計(jì)算屬性應(yīng)該是存取器,當(dāng)內(nèi)部的(私有的)狀態(tài)發(fā)生改變時(shí)它應(yīng)該發(fā)出通知。
再一次聲明,在web中應(yīng)該有一些庫(kù)來(lái)幫助我們進(jìn)行通知并且?guī)椭覀兏玫膶?shí)現(xiàn)計(jì)算屬性(以及減少模板的使用)。
我們?cè)谶@里會(huì)假設(shè)一個(gè)例子,這個(gè)例子中有一個(gè)circle
類。在這里,我們有一個(gè)citcle
,它有一個(gè)radius
屬性。在這里的情形中,radius
是一個(gè)存取器,并且當(dāng)它的值發(fā)生變化時(shí)它實(shí)際上會(huì)去通知自己值已經(jīng)發(fā)生變化了。這些通知將會(huì)連同其他變化被傳遞到這個(gè)對(duì)象或者其他對(duì)象。本質(zhì)上來(lái)說(shuō),如果你正在實(shí)現(xiàn)一個(gè)對(duì)象,你一定會(huì)想要擁有整合或者計(jì)算屬性的對(duì)象,或者你想要想出一個(gè)策略如何讓它運(yùn)行。一旦你做了這件事,它將會(huì)適應(yīng)你的整個(gè)系統(tǒng)。
看看下面的代碼在開發(fā)者工具中是如何運(yùn)行的:
function Circle(r) {
var radius = r;
var notifier = Object.getNotifier(this);
function notifyAreaAndRadius(radius) {
notifier.notify({
type: 'update',
name: 'radius',
oldValue: radius
})
notifier.notify({
type: 'update',
name: 'area',
oldValue: Math.pow(radius * Math.PI, 2)
});
}
Object.defineProperty(this, 'radius', {
get: function() {
return radius;
},
set: function(r) {
if (radius === r)
return;
notifyAreaAndRadius(radius);
radius = r;
}
});
Object.defineProperty(this, 'area', {
get: function() {
return Math.pow(radius, 2) * Math.PI;
},
set: function(a) {
r = Math.sqrt(a/Math.PI);
notifyAreaAndRadius(radius);
radius = r;
}
});
}
function observer(changes){
changes.forEach(function(change, i){
console.log(change);
})
}
在這里我們對(duì)于存取器屬性有一個(gè)簡(jiǎn)短的提示。在前面我們提到了對(duì)于數(shù)據(jù)屬性來(lái)說(shuō)只有值得變化是能夠被觀察到的。而存取器屬性和計(jì)算屬性則無(wú)法被觀察到。這是因?yàn)镴avaScript中的存取器并沒有真正的值的變化。一個(gè)存取器僅僅是一個(gè)函數(shù)集合。
如果你為一個(gè)存取器屬性賦值,你僅僅只是調(diào)用了這個(gè)函數(shù),并且在它看來(lái)值并沒有發(fā)生變化。它僅僅只是讓一些代碼運(yùn)行起來(lái)。
這里的問(wèn)題在于我們?cè)谏厦娴睦又袑⒋嫒∑鲗傩再x值為5.我們應(yīng)該能夠知道這里究竟發(fā)生了什么。這實(shí)際上是一個(gè)未解決的問(wèn)題。這個(gè)例子說(shuō)明了原因。對(duì)任何系統(tǒng)來(lái)說(shuō)知道這究竟意味著什么是不可能的,因?yàn)樵谶@里可以運(yùn)行任意代碼。每當(dāng)存取器屬性被訪問(wèn)時(shí),它的值都會(huì)發(fā)生改變,因此詢問(wèn)它什么時(shí)候會(huì)發(fā)生變化并沒有多大的意義。
Object.observe()
上的另一個(gè)模式是使用單個(gè)回調(diào)觀察者。這允許我們使用同一個(gè)回調(diào)函數(shù)堆多個(gè)不同的對(duì)象進(jìn)行觀察。這個(gè)回調(diào)函數(shù)在“微任務(wù)”的結(jié)尾將會(huì)把所有的變化都傳遞給它所觀察的對(duì)象。
也許你正在編寫一個(gè)非常大的應(yīng)用,并且經(jīng)常需要處理大規(guī)模的變化。此時(shí)我們希望用一種更加緊湊的方式來(lái)描述影響很多屬性的語(yǔ)義變化。
Object.observe()
使用兩個(gè)特定的函數(shù)來(lái)解決這個(gè)問(wèn)題:notifier.performChange()
以及notifier.notify()
,我們?cè)谏厦嬉呀?jīng)介紹過(guò)這兩個(gè)函數(shù)了。
我們可以從下面的例子中看到我們?nèi)绾蝸?lái)描述大規(guī)模變化,在這個(gè)例子中定義了一個(gè)叫做Thingy
的對(duì)象,其中包含幾個(gè)數(shù)計(jì)算功能(multiply, increment, incrementAndMultiply)。只要其中一個(gè)功能被使用,它就會(huì)告訴系統(tǒng)一些包含特定變化的事情發(fā)生了。
例如: notifier.performChange(‘foo’, performFooChangeFn)
function Thingy(a, b, c) {
this.a = a;
this.b = b;
}
Thingy.MULTIPLY = 'multiply';
Thingy.INCREMENT = 'increment';
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
Thingy.prototype = {
increment: function(amount) {
var notifier = Object.getNotifier(this);
// Tell the system that a collection of work comprises
// a given changeType. e.g
// notifier.performChange('foo', performFooChangeFn);
// notifier.notify('foo', 'fooChangeRecord');
notifier.performChange(Thingy.INCREMENT, function() {
this.a += amount;
this.b += amount;
}, this);
notifier.notify({
object: this,
type: Thingy.INCREMENT,
incremented: amount
});
},
multiply: function(amount) {
var notifier = Object.getNotifier(this);
notifier.performChange(Thingy.MULTIPLY, function() {
this.a *= amount;
this.b *= amount;
}, this);
notifier.notify({
object: this,
type: Thingy.MULTIPLY,
multiplied: amount
});
},
incrementAndMultiply: function(incAmount, multAmount) {
var notifier = Object.getNotifier(this);
notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
this.increment(incAmount);
this.multiply(multAmount);
}, this);
notifier.notify({
object: this,
type: Thingy.INCREMENT_AND_MULTIPLY,
incremented: incAmount,
multiplied: multAmount
});
}
}
我們可以為我們的對(duì)象定義兩個(gè)觀察者: 一個(gè)用來(lái)捕獲所有的變化,另一個(gè)將只會(huì)匯報(bào)我們定義的特定類型的變化 (Thingy.INCREMENT
, Thingy.MULTIPLY
, Thingy.INCREMENTANDMULTIPLY
)。
var observer, observer2 = {
records: undefined,
callbackCount: 0,
reset: function() {
this.records = undefined;
this.callbackCount = 0;
},
};
observer.callback = function(r) {
console.log(r);
observer.records = r;
observer.callbackCount++;
};
observer2.callback = function(r){
trueconsole.log('Observer 2', r);
}
Thingy.observe = function(thingy, callback) {
// Object.observe(obj, callback, optAcceptList)
Object.observe(thingy, callback, [Thingy.INCREMENT,
Thingy.MULTIPLY,
Thingy.INCREMENT_AND_MULTIPLY,
'update']);
}
Thingy.unobserve = function(thingy, callback) {
Object.unobserve(thingy);
}
我們現(xiàn)在可以開始玩弄一下代碼了。我們先定義一個(gè)新的Thingy
:
var thingy = new Thingy(2,4);
對(duì)它進(jìn)行觀察并進(jìn)行一些變化。有趣的事情發(fā)生了!
// Observe thingy
Object.observe(thingy, observer.callback);
Thingy.observe(thingy, observer2.callback);
// Play with the methods thingy exposes
thingy.increment(3); // { a: 5, b: 7 }
thingy.b++; // { a: 5, b: 8 }
thingy.multiply(2); // { a: 10, b: 16 }
thingy.a++; // { a: 11, b: 16 }
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }
位于這個(gè)perform function
中的一切東西都可以被看作是大型變化進(jìn)行的工作。接受大型變化的觀察者僅僅只會(huì)接受大型變化”記錄。那些不會(huì)接受底層變化的觀察者都來(lái)源于perform function
所做的事。
我們已經(jīng)討論了如何觀察一個(gè)對(duì)象,但是應(yīng)該如何觀察數(shù)組呢?
Array.observe()
是一個(gè)針對(duì)自身大型變化的方法 – 例如 – splice
,unshift
或者任何能夠隱式影響數(shù)組長(zhǎng)度的東西。在內(nèi)部它使用了notifier.performChange
(“splice”,…)。
下面是一個(gè)我們?nèi)绾斡^察一個(gè)模型數(shù)組的例子,當(dāng)?shù)讓訑?shù)據(jù)發(fā)生一些變化時(shí),我們將能夠得到一個(gè)變化的列表。
var model = ['Buy some milk', 'Learn to code', 'Wear some plaid'];
var count = 0;
Array.observe(model, function(changeRecords) {
count++;
console.log('Array observe', changeRecords, count);
});
model[0] = 'Teach Paul Lewis to code';
model[1] = 'Channel your inner Paul Irish';
考慮Object.observe()
性能的方式是將它想成讀緩存?;旧蟻?lái)說(shuō),在以下幾種情形中,一個(gè)緩存是最佳選擇(按照重要性排序):
Object.observe()
是為上述第一種情形設(shè)計(jì)的。
臟值檢查需要保留一個(gè)你所要觀察數(shù)據(jù)的副本。這意味著在臟值檢查中你需要一個(gè)額外的結(jié)構(gòu)內(nèi)存開銷。臟值檢查,一個(gè)作為權(quán)宜之計(jì)的解決方案,同時(shí)根本上也是一個(gè)脆弱的抽象,它可能會(huì)導(dǎo)致應(yīng)用中一些不必要的復(fù)雜性。
臟值檢查在任何數(shù)據(jù)可能發(fā)生變化的時(shí)候都必須要運(yùn)行。這很明顯并不是一個(gè)非常魯棒的方法,并且任何實(shí)現(xiàn)臟值檢查的途徑都是有缺陷的(例如,在輪詢中進(jìn)行檢查可能會(huì)造成視覺上的假象以及涉及到代碼的紊亂情況)。臟值檢查也需要注冊(cè)一個(gè)全局的觀察者,這很可能會(huì)造成內(nèi)存泄漏,而Object.observe()會(huì)避免這一點(diǎn)。
我們現(xiàn)在來(lái)看一些數(shù)據(jù)。
下面的基準(zhǔn)測(cè)試允許我們比較臟值檢查和Object.observe()
。圖中比較的數(shù)據(jù)是Observed-Object-Set-Size和Number-Of-Mutations。
總的結(jié)果表明:臟值檢查的性能和被觀察的對(duì)象成正比,而``Object.observe()的性能和我們所做的改變成正比。
Dirty-checking
Chrome with Object.observe() switched on
Object.observe()
現(xiàn)在已經(jīng)可以在Chrome 36 beta中使用,但是如果我們想要在其他瀏覽器中使用它該怎么辦?Polymer中的Observe-JS是一個(gè)針對(duì)于那些沒有原生實(shí)現(xiàn)Object.observe()
瀏覽器的一個(gè)墊片,但是它不僅僅是作為墊片,同時(shí)也包含了許多有用的語(yǔ)法糖。它提供了一種整合的視角,它能夠?qū)⑺凶兓偨Y(jié)起來(lái)并且提交一份關(guān)于變化的報(bào)告。它的好處主要體現(xiàn)在兩點(diǎn):
foo.bar.baz
,只要這個(gè)路徑的值發(fā)生了改變,你會(huì)得到通知。如果路徑是錯(cuò)誤的,將會(huì)返回undefined
。下面是一個(gè)例子:
var obj = { foo: { bar: 'baz' } };
var observer = new PathObserver(obj, 'foo.bar');
observer.open(function(newValue, oldValue) {
// respond to obj.foo.bar having changed value.
});
下面是一個(gè)例子
var arr = [0, 1, 2, 4];
var observer = new ArrayObserver(arr);
observer.open(function(splices) {
// respond to changes to the elements of arr.
splices.forEach(function(splice) {
splice.index; // index position that the change occurred.
splice.removed; // an array of values representing the sequence of elements which were removed
splice.addedCount; // the number of elements which were inserted.
});
});
正如上面所提到的,使用Object.observe()
能夠給予框架和庫(kù)中關(guān)于數(shù)據(jù)綁定的性能巨大的提升。
來(lái)自Ember的Yehuda Katz和Erik Bryn已經(jīng)確定將會(huì)在Ember最近的修改版本中添加對(duì)Object.observe()
的支持。來(lái)自Angular的Misko Hervy寫了一份關(guān)于Angular 2.0的設(shè)計(jì)文檔,其中的內(nèi)容關(guān)于改善變化探測(cè)(change detection)。在將來(lái),當(dāng)Object.observe()
在Chrome穩(wěn)定版中出現(xiàn)時(shí),Angular會(huì)使用Object.observe()
來(lái)實(shí)現(xiàn)變化探測(cè)的功能,在此之前它們會(huì)選擇使用Watchtower.js – Angular自己的變化探測(cè)的實(shí)現(xiàn)方式。實(shí)在是太令人激動(dòng)了。
Object.observe()
是一個(gè)添加到web平臺(tái)上非常強(qiáng)大的特性,你現(xiàn)在就可以開始使用它。
我們希望這項(xiàng)特征能夠及時(shí)的登陸到更多的瀏覽器中,它能夠允許JavaScript框架從本地對(duì)象觀察的能力中獲得更多性能上的提升。Chrome 36 beta及其以上的版本都能使用這項(xiàng)特性,在未來(lái)Opera發(fā)布的版本中這項(xiàng)特性也會(huì)得到支持。
現(xiàn)在就和JavaScript框架作者談?wù)?code>Object.observe()如何能夠提高他們框架中數(shù)據(jù)綁定的性能。未來(lái)還有更多讓人激動(dòng)的時(shí)刻。
更多建議: