這篇文章主要介紹了AngularJS雙向資料綁定原理之$watch、$apply和$digest的應用,具有一定的參考價值,有興趣的小夥伴們可以參考一下
引子
這篇文章是寫給AngularJS新手的,如果你已經對AngularJS的雙向資料綁定有了深入的了解,直接去閱讀原始碼好了。
背景
AngularJS開發者都想知道雙向資料綁定是怎麼實現的。與data-binding相關的術語琳瑯滿目: $watch,$apply,$digest,dirty-checking等等它們是如何運作的呢?讓我們從頭開始講起吧
AngularJS 的雙向資料綁定是被瀏覽器逼的
瀏覽器看上去很美,其實在資料互動這塊兒,由於瀏覽器的“不作為”,導致瀏覽器的資料刷新成為一個難題。具體來說,瀏覽器可以輕鬆監聽一個事件,例如:使用者點擊一個按鈕,或是在輸入框裡輸入東西,為此還提供了事件回調函數的API,事件的回調函數就會在javascript解釋器裡執行;但反過來就沒這麼簡單了,如果來自後台的數據發生了變化,需要通知給瀏覽器,讓瀏覽器刷新,瀏覽器並沒有提供這樣的數據交互機制,對於開發者來說,這是一個難以克服的障礙,該怎麼辦呢? AngularJS出現了,它透過$scope 很好地實現了雙向資料綁定,背後的原理就是$watch,$apply,$digest,dirty-checking
##$watch 佇列($watch list)
從字面上看,watch 是觀察的意思。每次將東西綁定到瀏覽器時,就會往$watch佇列插入一張$watch。想像一下$watch就是那個可以偵測它監視的model裡時候有變化的東西。例如你有如下的程式碼User: <input type="text" ng-model="user" /> Password: <input type="password" ng-model="pass" />這裡有個$scope.user,它被綁定在了第一個輸入框上,還有個$scope.pass,它被綁定在了第二個輸入框上;然後在$watch list裡面加入兩個$watch:建立一個controllers.js 文件,程式碼如下:
app.controller('MainCtrl', function($scope) { $scope.foo = "Foo"; $scope.world = "World"; });對應的html 檔案, index.html 程式碼如下:
Hello, {{ World }}這裡,即使在$scope上添加了兩個東西,但是只有一個綁定在了UI上,因此只生成了一個$watch. 再看下面的例子:controllers.js
app.controller('MainCtrl', function($scope) { $scope.people = [...]; });對應的html檔index.html
<ul> <li ng-repeat="person in people"> {{person.name}} - {{person.age}} </li> </ul>#這樣看來,又產生了多個$watch。每個person有兩個(一個name,一個age),然後ng-repeat是一個循環,因此10個person一共是(2 * 10) 1,也就是說有21個$watch。因此,每一個綁定到了瀏覽器上的資料都會產生一個$watch。對,那這寫$watch是什麼時候產生的呢?先回顧下AngularJS的載入原理
AngularJS的載入原理:
AngularJS的範本載入分為編譯(compile)和連結(linking)兩個階段,在linking階段,AngularJS解譯器會尋找每個directive,然後產生每個需要的$watch。對了,$watch就是在這個階段生成的。 接下來,開始用到$digest了$digest 循環
從字面上看,digest是「消化」的意思,總感覺這個名字怪怪的,跟不可思議的是dirty-checking, 字面意思“髒檢查”,還是不翻譯為好。原作者的本意絕對不是這個意思,只可意會不可言傳! $digest 是一個循環,它在循環做什麼呢? $digest 在遍歷我們的$watch。 $digest 一個個問$watch —— 「嗨,你觀察的資料改變了沒?」這個遍歷就是所謂的dirty-checking。既然所有的$watch都檢查完了,那就要問了:有沒有$watch更新過?如果有至少一個更新過,這個循環就會再次觸發,直到所有的$watch都沒有變化。這樣就能夠保證每個model都已經不會再改變。記住如果循環超過10次的話,它將會拋出一個異常,以免出現無限循環。當$digest循環結束時,DOM會相應地變化。 看段程式碼,例如: controllers.jsapp.controller('MainCtrl', function() { $scope.name = "Foo"; $scope.changeFoo = function() { $scope.name = "Bar"; } });對應的html文件,index.html
{{ name }} <button ng-click="changeFoo()">Change the name</button>這裡只有一個$watch,因為ng-click不產生$ watch(函數是不會變的)。 $digest 執行的流程是:
$apply 的應用程式
Angular context 是整个Angular的上下文,也可以把它理解为Angular容器,那么,是谁来决定哪些事件可以进入 Angular Context,哪些事件又不能进入呢? 其控制器在 $apply手上。
如果当事件触发时,调用$apply,它会进入angular context,如果没有调用就不会进入。你可能会问:刚才的例子并没有调用$apply,这是怎么回事呢?原来,是Angular背后替你做了。当点击带有ng-click的元素时,事件就会被封装到一个$apply调用中。如果有一个ng-model="foo"的输入框,当输入一个字母 f 时,事件就会这样调用,$apply("foo = 'f';")。
$apply的应用场景
$apply是$scope的一个函数,调用它会强制一次$digest循环。如果当前正在执行$apply循环,则会抛出一个异常。
如果浏览器上数据没有及时刷新,可以通过调用$scope.$apply() 方法,强行刷新一遍。
通过 $watch 监控自己的$scope
<!DOCTYPE html> <html ng-app="demoApp"> <head> <title>test</title> <!-- Vendor libraries --> <script src="lib/jquery-v1.11.1.js"></script> <script src="lib/angular-v1.2.22.js"></script> <script src="lib/angular-route-v1.2.22.js"></script> </head> <body> <p ng-controller="MainCtrl" > <input ng-model="name" /> Name updated: {{updated}} times. </p> <script > var demoApp = angular.module('demoApp',[]); demoApp.controller('MainCtrl', function($scope) { $scope.name = "Angular"; $scope.updated = -1; $scope.$watch('name', function() { $scope.updated++; }); }); </script> </body> </html>
代码说明:
当controller 执行到 $watch时,它会立即调用一次,所以把updated的值设为 -1 。 上输入框中输入字符发生变化时,你会看到 updated 的值随之变化,而且能显示变化的次数。
$watch 检测到的数据变化
小结
我们对 AngularJS的双向数据绑定有了一个初步的认识,对于AngularJS来说,表面上看操作DOM很简单,其实背后有 $watch、$digest 、 $apply 三者在默默地起着作用。这个遍历检查数据是否发生变化的过程,称之为:dirty-checking。 当你了解了这个过程后,你会对它嗤之以鼻,感觉这种方法好low 哦。 确实,如果一个DOM中有 2000- 3000个 watch,页面的渲染速度将会大打折扣。
这个渲染的性能问题怎么解决呢?随着ECMAScript6的到来,Angular 2 通过Object.observe 极大地改善$digest循环的速度。或许,这就是为什么 Angular 团队迫不及待地推出 Angular 2 的原因吧。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是AngularJS雙向資料綁定原理(詳細教學)的詳細內容。更多資訊請關注PHP中文網其他相關文章!