Object.observe()帶來的數(shù)據(jù)綁定變革

2018-06-09 16:21 更新

引言

一場變革即將到來。Javascript中的一項新特性將會顛覆之前你對于數(shù)據(jù)綁定的所有認(rèn)識。它也將改變你所使用的MVC庫觀察模型中發(fā)生的修改以及更新的實現(xiàn)方式。你會看到,那些所有在意屬性觀察的應(yīng)用性能將會得到巨大的提升。

我們很高興的看到,Object.observe()已經(jīng)正式加入到了Chrome 36 beta版本中。

Object.observe()是未來ECMAScript標(biāo)準(zhǔn)之一,它是一個可以異步觀察Javascript中對象變化的方法,而無需使用一個其他的JS庫。它允許一個觀察者接收一個按照時間排序的變化記錄序列,這個序列描述的是一列被觀察的對象所發(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)被觀察的對象發(fā)生任何變化時,回調(diào)函數(shù)將會匯報這些變化:

通過使用Object.observe(),你可以不需要使用任何框架就能實現(xiàn)雙向數(shù)據(jù)綁定。

但這并不意味著你就不應(yīng)該使用一個框架。對于一些有著復(fù)雜業(yè)務(wù)邏輯的項目,經(jīng)過精心設(shè)計的框架的重要性不言而喻,你應(yīng)該繼續(xù)使用它們。這些框架減輕了開發(fā)新手的工作量,而且只需要編寫很少的代碼就能夠維護(hù)和實現(xiàn)一些模式并達(dá)到我們想要的目的。如果你不需要一個框架,你可以使用一個體積更小,針對性更強的庫,比如Polymer(它已經(jīng)開始使用Object.observe()了)。

即便你已經(jīng)重度依賴于一個MV*框架,Object.observe()仍然能為你的應(yīng)用帶來一些性能各方面的提升,它能夠更快更簡單的實現(xiàn)一些功能并維持同樣的API。例如,在Angular以前的一個Benchmark測試中,對于一個Model中發(fā)生的變化,臟值檢查對每次更新會花費40ms,而Object.observe()只會花費1-2ms(相當(dāng)于20-40倍的性能提升)。

不需要冗長代碼來實現(xiàn)的數(shù)據(jù)雙向綁定還意味著你不需要通過輪詢來發(fā)現(xiàn)變化,這將帶來更長的電池使用時間!

如果你已經(jīng)對Object.observe()有了一些了解,可以直接跳過簡介這一節(jié),或者接著閱讀了解更多關(guān)于它能夠解決的問題。

我們要需要觀察什么?

當(dāng)我們在討論數(shù)據(jù)觀察時,我們通常指的是對一些特定類型的數(shù)據(jù)變化保持關(guān)注:

  • 原始JavaScript對象中的變化
  • 當(dāng)屬性被添加、改變、或者刪除時的變化
  • 當(dāng)數(shù)組中的元素被添加或者刪除時的變化
  • 對象的原型發(fā)生的變化

數(shù)據(jù)綁定的重要性

當(dāng)你開始關(guān)心模型-視圖的控制分離時,數(shù)據(jù)綁定就會變成一件重要的事。HTML是一個非常好的聲明機制,但是它完全是靜態(tài)的。理想狀態(tài)下,你想要在數(shù)據(jù)和DOM之間聲明它們的關(guān)系,以便讓DOM保持更新。這會讓你節(jié)省很多由于寫一些重復(fù)代碼而浪費的時間。

當(dāng)你擁有一個復(fù)雜的用戶界面,你需要理清楚許多數(shù)據(jù)屬性和許多視圖中元素的關(guān)系時,數(shù)據(jù)綁定是非常有用的。這在我們今天需要創(chuàng)建的單頁應(yīng)用中非常常見。

通過在瀏覽器中原生的觀察數(shù)據(jù),我們給予了JavaScript框架(或者你編寫的一些功能庫)一種方式來實現(xiàn)對模型數(shù)據(jù)的變化進(jìn)行觀察而不需要依賴于我們今天正在使用的一些hack方法。

現(xiàn)在使用的數(shù)據(jù)綁定方案是什么樣子的

臟值檢查

你以前曾經(jīng)在那里看到過數(shù)據(jù)綁定?如果你在你的web應(yīng)用中使用過一個現(xiàn)代MV*框架(例如Angular,Knockout),那么你或許已經(jīng)使用過數(shù)據(jù)綁定將數(shù)據(jù)綁定到你的DOM上了。為了復(fù)習(xí)一下,下面是一個電話列表應(yīng)用的例子,在其中我們會將一個phones數(shù)組中的值(在JavaScript中定義)綁定到一個列表項目中以便于我們的數(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的地址在這里。

在任何時候,只要是底層的model數(shù)據(jù)發(fā)生了變化,我們在DOM中的列表也會跟著更新。Angular是怎么做到這一點的呢?在Angular的背后,有一個叫做臟值檢查的東西。

臟值檢查的基本原理就是只要任何時候數(shù)據(jù)發(fā)生了變化,這個庫都會通過一個digest或者change cycle去檢查變化是否發(fā)生了。在Angular中,一個digest循環(huán)意味著所有被監(jiān)視的表達(dá)式都會被循環(huán)一遍以便查看其中是否有變化發(fā)生。它知道一個模型之前的值,因此當(dāng)變化發(fā)生時一個change事件將會被觸發(fā)。對于開發(fā)者來說,這帶來的一大好處就是你可以使用原生的JavaScript對象數(shù)據(jù),它易于使用及整合。下面的圖片展示的是一個非常糟糕的算法,它的開銷非常大。

這個操作的開銷和被監(jiān)視的對象的數(shù)量是成正比的。我們可能需要做很多的臟治檢查。同時我也需要一種方式去觸發(fā)臟值檢查,當(dāng)某些數(shù)據(jù)可能發(fā)生改變時。有很多的框架使用了一些非常聰明的方法來解決這個問題,但是它們是否足夠好目前還尚無定論。

web生態(tài)系統(tǒng)應(yīng)該擁有更多的能力去創(chuàng)新和進(jìn)化它自己的聲明機制,例如:

  • 有約束的模型系統(tǒng)
  • 自動的保存系統(tǒng)(例如:將變化保存在IndexedDB或者localStorage中)
  • 容器對象(Ember,Backbone)

容器對象是一個框架創(chuàng)建的對象,它能夠在其中保存一些數(shù)據(jù)。它們擁有一些存取器去獲取數(shù)據(jù)并且能夠在你設(shè)置或者獲取對象時捕獲到這些行為并在內(nèi)部進(jìn)行廣播。這是一種非常好的方式。它的性能很好,從算法上來說也不錯。下面是一個使用Ember容器對象的一個簡單例子:


// 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)在你存在的另一個問題是你需要使用不同種類的對象??偟膩碚f你需要將從服務(wù)器獲取的數(shù)據(jù)進(jìn)行轉(zhuǎn)換以便它們是能夠被觀察到的。

目前的JS代碼并不能很好的整合生成數(shù)據(jù),因為這些代碼一般會假設(shè)它們操作的是原生JavaScript對象,而不是一些特定的對象類似類型。

介紹Object.observe()

我們真正想要的可能是兩個世界中最好的東西 – 一種支持對原生數(shù)據(jù)對象(普通JavaScript對象)進(jìn)行觀察的方法,同時不需要每次都對所有東西進(jìn)行臟值檢查。它需要有良好的算法表現(xiàn)。它還需要能夠很好的整合到各個平臺中。這些都是Object.observe()能夠帶給我們的東西。

它允許我們對一個對象或者變異屬性進(jìn)行觀察,并且在變化發(fā)生時得到及時通知。但是我們在這里不想看什么理論,讓我們來看看代碼!

Object.observe()和Object.unobserve()

讓我們假設(shè)我們現(xiàn)在有一個簡單的JavaScript對象,它代表一個模型:


// A model can be a simple vanilla object
var todoModel = {
    label: 'Default',
    completed: false
};

我們可以制定一個比回調(diào)函數(shù),用來處理對象上的變化:


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)用時,被觀察的對象可能已經(jīng)發(fā)生了多次改變,因此對于每一次變化,新的值(即每次變化以后的值)和當(dāng)前值(最終的值)并不一定是相同的。

我們可以使用Object.observe()來觀察這些變化,只要將對象作為第一個參數(shù),而將回調(diào)函數(shù)作為第二個參數(shù):


Object.observe(todoModel, observer);

我們現(xiàn)在對我們的Todos的模型對象做一些改變:


todoModel.label = 'Buy some more milk';

看看控制臺,我們現(xiàn)在得到了一些有用的信息!我們知道什么屬性發(fā)生了變化,它是怎樣變化的以及新的值是什么。

再見,臟值檢查!你的墓碑應(yīng)該被刻上Comic Sans字體。我們再來改變其他的屬性。這次改變的是completeBy:


todoModel.completeBy = '01/01/2014';

正如我們所見的,我們又再一次得到了關(guān)于變化的報告:

非常好。要是我們現(xiàn)在決定從對象中刪除completed屬性會怎么樣:


delete todoModel.completed;

正如我們所見的,返回的變化報告包含了關(guān)于刪除的信息。正如我們所期待的,新的值現(xiàn)在是undefined。那么,我們現(xiàn)在知道了你可以知道屬性什么時候被添加。什么時候被刪除?;旧蟻碚f,你可以知道一個對象上的屬性集(’new’,’deleted’,’recongigured’)以及它的原型(proto)的變化。

在任何觀察系統(tǒng)中,總是存在一個方法來停止觀察。在這里,我們有Object.unobserve()方法,它的用法和Object.observe()一樣但是可以像下面一樣被調(diào)用:


Object.unobserve(todoModel, observer);

正如下面所示,在使用該方法之后,任何的變化都不再作為一個變化列表記錄返回。

指定感興趣的變化

現(xiàn)在我們已經(jīng)了解到了我們?nèi)绾稳カ@取一個被觀察對象的變化列表。但是如果我們僅僅只對一個對象中的某些屬性感興趣該怎么辦?人人都需要一個垃圾郵件過濾器。Observer可以通過一個列表指定一些我們想要看到的變化。我們需要通過Object.observe()第三個參數(shù)來指定:


Object.observe(obj, callback, opt_acceptList)

現(xiàn)在我們來看一個如何使用的例子:


// 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

如果我們刪除了這個標(biāo)簽,注意到這個類型的變化將會被報告:

1
delete todoModel.label;

如果你不指定一個列表,它默認(rèn)將會報告固有的對象變化類型 (“add”, “update”, “delete”, “reconfigure”, “preventExtensions” (丟與那些不可擴(kuò)展的對象是不可觀察的))。

通知

Object.observe()也帶有一些通知。它們并不像是你在手機上看到了通知,而是更加有有用。通知和變異觀察者比較類似。它們發(fā)生在微任務(wù)的結(jié)尾。在瀏覽器的上下文,它幾乎總是位于當(dāng)前事件處理器的結(jié)尾。

這個時間點非常的重要因為基本上來說此時一個工作單元已經(jīng)結(jié)束了,現(xiàn)在觀察者已經(jīng)開始它們的共走了。這是一個非常好的回合處理模型。

使用一個通知器的工作流程如下所示:

現(xiàn)在我們通過一個例子來如何通過自定義一個通知器來處理一個對象的屬性被設(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
// 定義一個單獨的變量,我們即將使用它來作為我們的模型中的getter
var _b = 2;
// Define a new property 'b' under 'a' with a custom getter and setter
// 在'a'下面定義一個新的屬性'b',并自定義一個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è)置時,注意一個特定類型的變化將會發(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ā)生變化時將會輸出信息
        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ā)生變化時(‘update’)我們將會得到報告。以及任何對象的實現(xiàn)也將會被報告(notifier.notifyChange())。

多年的web平臺開發(fā)經(jīng)驗告訴我們整合方法是你應(yīng)該最先嘗試的事情,因為它最容易去實現(xiàn)。但是它存在的問題是以它會創(chuàng)造一個從根本上來看就很未下的處理模型。如果你正在編寫代碼并且更新了一個對象的屬性,你實際上并不想陷入這樣一種困境:更新模型中的屬性會最終導(dǎo)致任意一段代碼去做任意一件事情。當(dāng)你的函數(shù)正好運行到一半時,假設(shè)失效并不是什么理想的狀況。

如果你是一個觀察者,你并不想當(dāng)某人正在做某事的時候被調(diào)用。你并不像在不連續(xù)的狀態(tài)下被調(diào)用。因為這最終往往會導(dǎo)致更多的錯誤檢查。你應(yīng)該試著去容忍更多的情形,并且基本上來說它是一個很難去合作的模型。異步是一件更難處理的事情但是最終它會產(chǎn)生更好的模型。

上述問題的解決辦法是變化合成記錄(synthetic change records)。

變化合成記錄

基本上來說,如果你想要存取器或者計算屬性的話,你應(yīng)該復(fù)雜在這些值發(fā)生改變時發(fā)出通知。這會導(dǎo)致一些額外的工作,但是它是這種機制第一類的特征,并且這些通知會連同來自余下的底層數(shù)據(jù)對象的通知一起被發(fā)布出來。

觀察存取器或者計算屬性的問題可以通過使用notifier.notify來解決 – 它也是Object.observe()的另外一部分。大多數(shù)的觀察系統(tǒng)想要某些形式的觀察導(dǎo)出值。有很多方法可以實現(xiàn)它。Object.observe()并沒有用正確的方式進(jìn)行判斷。計算屬性應(yīng)該是存取器,當(dāng)內(nèi)部的(私有的)狀態(tài)發(fā)生改變時它應(yīng)該發(fā)出通知。

再一次聲明,在web中應(yīng)該有一些庫來幫助我們進(jìn)行通知并且?guī)椭覀兏玫膶崿F(xiàn)計算屬性(以及減少模板的使用)。

我們在這里會假設(shè)一個例子,這個例子中有一個circle類。在這里,我們有一個citcle,它有一個radius屬性。在這里的情形中,radius是一個存取器,并且當(dāng)它的值發(fā)生變化時它實際上會去通知自己值已經(jīng)發(fā)生變化了。這些通知將會連同其他變化被傳遞到這個對象或者其他對象。本質(zhì)上來說,如果你正在實現(xiàn)一個對象,你一定會想要擁有整合或者計算屬性的對象,或者你想要想出一個策略如何讓它運行。一旦你做了這件事,它將會適應(yīng)你的整個系統(tǒng)。

看看下面的代碼在開發(fā)者工具中是如何運行的:


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

存取器屬性

在這里我們對于存取器屬性有一個簡短的提示。在前面我們提到了對于數(shù)據(jù)屬性來說只有值得變化是能夠被觀察到的。而存取器屬性和計算屬性則無法被觀察到。這是因為JavaScript中的存取器并沒有真正的值的變化。一個存取器僅僅是一個函數(shù)集合。

如果你為一個存取器屬性賦值,你僅僅只是調(diào)用了這個函數(shù),并且在它看來值并沒有發(fā)生變化。它僅僅只是讓一些代碼運行起來。

這里的問題在于我們在上面的例子中將存取器屬性賦值為5.我們應(yīng)該能夠知道這里究竟發(fā)生了什么。這實際上是一個未解決的問題。這個例子說明了原因。對任何系統(tǒng)來說知道這究竟意味著什么是不可能的,因為在這里可以運行任意代碼。每當(dāng)存取器屬性被訪問時,它的值都會發(fā)生改變,因此詢問它什么時候會發(fā)生變化并沒有多大的意義。

使用一個回調(diào)函數(shù)觀察多個對象

Object.observe()上的另一個模式是使用單個回調(diào)觀察者。這允許我們使用同一個回調(diào)函數(shù)堆多個不同的對象進(jìn)行觀察。這個回調(diào)函數(shù)在“微任務(wù)”的結(jié)尾將會把所有的變化都傳遞給它所觀察的對象。

大規(guī)模的變化

也許你正在編寫一個非常大的應(yīng)用,并且經(jīng)常需要處理大規(guī)模的變化。此時我們希望用一種更加緊湊的方式來描述影響很多屬性的語義變化。

Object.observe()使用兩個特定的函數(shù)來解決這個問題:notifier.performChange()以及notifier.notify(),我們在上面已經(jīng)介紹過這兩個函數(shù)了。

我們可以從下面的例子中看到我們?nèi)绾蝸砻枋龃笠?guī)模變化,在這個例子中定義了一個叫做Thingy的對象,其中包含幾個數(shù)計算功能(multiply, increment, incrementAndMultiply)。只要其中一個功能被使用,它就會告訴系統(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
    });
  }
}

我們可以為我們的對象定義兩個觀察者: 一個用來捕獲所有的變化,另一個將只會匯報我們定義的特定類型的變化 (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)在可以開始玩弄一下代碼了。我們先定義一個新的Thingy


var thingy = new Thingy(2,4);

對它進(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 }

位于這個perform function中的一切東西都可以被看作是大型變化進(jìn)行的工作。接受大型變化的觀察者僅僅只會接受大型變化”記錄。那些不會接受底層變化的觀察者都來源于perform function所做的事。

觀察數(shù)組

我們已經(jīng)討論了如何觀察一個對象,但是應(yīng)該如何觀察數(shù)組呢?

Array.observe()是一個針對自身大型變化的方法 – 例如 – splice,unshift或者任何能夠隱式影響數(shù)組長度的東西。在內(nèi)部它使用了notifier.performChange(“splice”,…)。

下面是一個我們?nèi)绾斡^察一個模型數(shù)組的例子,當(dāng)?shù)讓訑?shù)據(jù)發(fā)生一些變化時,我們將能夠得到一個變化的列表。


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()性能的方式是將它想成讀緩存?;旧蟻碚f,在以下幾種情形中,一個緩存是最佳選擇(按照重要性排序):

  1. 讀的頻率決定著寫的頻率
  2. 你可以創(chuàng)造一個緩存,它可以在讀數(shù)據(jù)期間將涉及到寫數(shù)據(jù)的操作進(jìn)行算法上的優(yōu)化
  3. 寫數(shù)據(jù)減慢的時間常數(shù)是可以接受的

Object.observe()是為上述第一種情形設(shè)計的。

臟值檢查需要保留一個你所要觀察數(shù)據(jù)的副本。這意味著在臟值檢查中你需要一個額外的結(jié)構(gòu)內(nèi)存開銷。臟值檢查,一個作為權(quán)宜之計的解決方案,同時根本上也是一個脆弱的抽象,它可能會導(dǎo)致應(yīng)用中一些不必要的復(fù)雜性。

臟值檢查在任何數(shù)據(jù)可能發(fā)生變化的時候都必須要運行。這很明顯并不是一個非常魯棒的方法,并且任何實現(xiàn)臟值檢查的途徑都是有缺陷的(例如,在輪詢中進(jìn)行檢查可能會造成視覺上的假象以及涉及到代碼的紊亂情況)。臟值檢查也需要注冊一個全局的觀察者,這很可能會造成內(nèi)存泄漏,而Object.observe()會避免這一點。

我們現(xiàn)在來看一些數(shù)據(jù)。

下面的基準(zhǔn)測試允許我們比較臟值檢查Object.observe()。圖中比較的數(shù)據(jù)是Observed-Object-Set-SizeNumber-Of-Mutations。

總的結(jié)果表明:臟值檢查的性能和被觀察的對象成正比,而``Object.observe()的性能和我們所做的改變成正比。

Dirty-checking

Chrome with Object.observe() switched on

為Object.observe()提供墊片

Object.observe()現(xiàn)在已經(jīng)可以在Chrome 36 beta中使用,但是如果我們想要在其他瀏覽器中使用它該怎么辦?Polymer中的Observe-JS是一個針對于那些沒有原生實現(xiàn)Object.observe()瀏覽器的一個墊片,但是它不僅僅是作為墊片,同時也包含了許多有用的語法糖。它提供了一種整合的視角,它能夠?qū)⑺凶兓偨Y(jié)起來并且提交一份關(guān)于變化的報告。它的好處主要體現(xiàn)在兩點:

  1. 你可以觀察路徑。這意味著你可以說,我想要從一個給定的對象中觀察foo.bar.baz,只要這個路徑的值發(fā)生了改變,你會得到通知。如果路徑是錯誤的,將會返回undefined

下面是一個例子:


  1. 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.
    });
  2. 能夠告訴你數(shù)組的拼接。數(shù)組拼接基本上來說是你為了將舊版本數(shù)組轉(zhuǎn)換為新版本數(shù)組是需要進(jìn)行了最基本的拼接操作。這是一種轉(zhuǎn)換的類型或者是這個數(shù)組的不同視圖。它是你想要將數(shù)組從舊狀態(tài)變?yōu)樾聽顟B(tài)時需要進(jìn)行的最基本的工作。

下面是一個例子


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()

正如上面所提到的,使用Object.observe()能夠給予框架和庫中關(guān)于數(shù)據(jù)綁定的性能巨大的提升。

來自Ember的Yehuda Katz和Erik Bryn已經(jīng)確定將會在Ember最近的修改版本中添加對Object.observe()的支持。來自Angular的Misko Hervy寫了一份關(guān)于Angular 2.0的設(shè)計文檔,其中的內(nèi)容關(guān)于改善變化探測(change detection)。在將來,當(dāng)Object.observe()在Chrome穩(wěn)定版中出現(xiàn)時,Angular會使用Object.observe()來實現(xiàn)變化探測的功能,在此之前它們會選擇使用Watchtower.js – Angular自己的變化探測的實現(xiàn)方式。實在是太令人激動了。

總結(jié)

Object.observe()是一個添加到web平臺上非常強大的特性,你現(xiàn)在就可以開始使用它。

我們希望這項特征能夠及時的登陸到更多的瀏覽器中,它能夠允許JavaScript框架從本地對象觀察的能力中獲得更多性能上的提升。Chrome 36 beta及其以上的版本都能使用這項特性,在未來Opera發(fā)布的版本中這項特性也會得到支持。

現(xiàn)在就和JavaScript框架作者談?wù)?code>Object.observe()如何能夠提高他們框架中數(shù)據(jù)綁定的性能。未來還有更多讓人激動的時刻。

參考鏈接



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號