谈谈Angular关于$watch,$apply 以及 $digest的工作原理
这篇文章主要是面向那些刚开始学AngularJs和想要了解数据绑定(data-binding)是怎么工作的,
如果你已经熟悉如何使用angularjs了,我强烈建议你不用阅读了。
angularjs使用者想要知道data-binding是如何工作的,就会遇到很多的关的术语
比如$wacth,$apply,$digest,dirty-checking(脏值检测)...等等,这些又是做什么的呢?
在这篇文章里我会解决所有的疑问,通过结合这些术语在一起来学习。
但是我会尽量用简单的方式来说明。
现在开始
The browser events-loop and the Angular.js extension
我们的浏览器会检测等待事件发生,比如用户的一些行为,假如你点击了一个button或者在input写东西,
事件的回调就会在内置的JavaScript跑起来,然后你就能够做一些DOM操作了。
所以当回调发生的时候,浏览器中的DOM会发生一些变化。
而Angularjs扩展了这个事件轮询,创建了一个叫angular content的东西(记住它,非常重要的一个概念),
为了解释这个context是什么以及它是怎么工作的,我们需要先了解一下其他的一些概念。
The $watch list
每当你在ui上绑定了东西,就会添加了一个$wacth到$watch list中
你可以把$watch想象成为一个能够察觉model的变化的检测器,
比如你的html代码是
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
在这里,我们将$scope.user绑定到了第一个input上,把$scope.pass绑定到了第二个input上,
这样就意味着,我们添加了两个$wacth到了$watch list中了。
再比如
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
//页面htnl
Hello, {{ World }}
这个例子中,虽然我们在控制器中定义了foo和world,但是只有一个绑定到了页面上,
所以这个例子中,我们只创建了一个$watch。
我们再来看看下面这种情况:
app.controller('MainCtrl', function($scope) {
$scope.people = [...];
}); //HTML代码
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>
那这个例子有多少个$watch被创建呢?
我们在这里假设peple数组中的每个元素都拥有name和age这2个属性。
由于我们实用ng-repeat遍历$scope.people,
如果有10个人,则我们一共创建了10*2+1 = 22
注意:其中的1,为ng-repeat所创建的。
所以要注意的是,当我们在ui上使用(绑定)用directives 指令的时候,就创建了一个$watch,
对了,那它是什么时候创建的呢?
当我们的模板加载完成(亦叫做linking phase),compiler就会找到所有的指令,并创建对应的$watch。
$digest loop
还记得我们在上面讨论的event-loop吗?
当浏览器发送一个事件,我们就能通过angular context管理这个事件,此时$digest就会被激活
这个事件轮询loop由两个小loop组成,
一个是处理$evalAsync队列
一个是处理$watch list,这就是本文的主题了。
那处理的过程是怎样的?$digest会轮询我们有的$watch list,大概就想下面这样
---》Hey,$watch,你的value值是多少啊?
---》我的value值是9。
---》好的,有变化吗?
---》没有。
(什么都没有变化,就会跳到下个继续询问)
---》来,你的value值是多少啊?
---》是foo。
---》有变化吗?
---》有啊,本来是bar的
(很好,那么现在我们有一个DOM要更新下了)
流程就会这样继续下去,知道$watch list中所有的$watch都被询问...
现在讲下脏值检测(dirty-checking),现在所有的$watch都被轮询过了,
会再次询问是否所有的$watch都已经更新了?
如果其中有一个改变了,就会重新轮询,直到所有的$wacth没有改变。这样做事为了确保所有的model都是“干净的”
注意:如果轮询loop超过十次,就会抛出异常,来退出无限的轮询。
当$degest loop完成,DOM就会发生改变
看下面的例子
app.controller('MainCtrl', function() {
$scope.name = "Foo"; $scope.changeFoo = function() {
$scope.name = "Bar";
}
});
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
这里我们只有一个$watch,,因为ng-click不会创建任何的$watch(函数function是不会发生改变的)
1·当我们点击按钮的时候
2·浏览器发送事件,进入angular context(后面再解释这个context)
3·然后执行$degest loop会轮询所有的$watch是否发生改变
4·新的一轮loop报告说没有新的改变了
5·然后浏览器就会拿回控制权,然后更新DOM,这里是更新新的值,$scope.name
这里最重要的一点(也是难点)是所有的事件会进入angular context然后执行$digest loop
这意味着每当我们在input键入一个字符,轮询loop会检查页面上所有的$watch。
Entering the angular context with $apply
那是怎么进入angular context的呢?答案是$apply
当一个事件发生时吗,你如果调用$apply,就会进入angular context。
如果你没有调用,就只会在外部(这里指的是区别于angular context内部)执行
那么你就可能会有疑问了,
上面的那个例子我没有调用$apply,它为什么会进入angular context呢?答案是Angular帮我们做了。
所有当你调用ng-click时候,这个事件就会被包含进了$apply调用了。
如果你在一个页面上有input,并且标签上写着ng-model="foo",
然后输入“f”,事件就像这样$apply("foo = 'f';")被调用,换句话说,就是被$apply包含着调用了。
When angular doesn’t use $apply for us
这是很多Angular的新手共同的疑问,我在页面上上使用了jQuery,为什么JQuery没有更新我的绑定呢?
因为jQuery没有调用$apply,所有的事件并没有进入angular context中,所以$digest loop没有执行
下面让我们看看一个有趣的例子:
假如我们的代码中有下面的指令directive 和控制器controller
app.directive('clickable', function() { return {
restrict: "E",
scope: {
foo: '=',
bar: '='
},
template: '<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.foo++;
scope.bar++;
});
}
} }); app.controller('MainCtrl', function($scope) {
$scope.foo = 0;
$scope.bar = 0;
});
例子中我们绑定了foo和bar到了ul上的li中,我们想每当我们点击的时候,foo和bar都会增加一
那实际情况中我们点击之后会发生什么呢?我们能不能看到foo和bar按照我们预期的变化呢?
答案是不会,因为上面的click是没有被包含在$apply中调用的。
这样是否意味着我们不能这样控制我们想要的变化呢?实际上不是的,我们是可以的
实际上$scope上的foo和bar都是变化了的,只是它没有执行$digest loop,所以也就没有意识到$watch的变化
这样也意味着,如果我在之后调用$apply,这样所有的$watch都会知道变化,然后就会更新相应的DOM
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="http:////cdn.staticfile.org/angular.js/1.2.10/angular.min.js"></script>
<meta charset=utf-8 />
<title>Directive example</title>
</head>
<body ng-controller="MainCtrl">
<clickable foo="foo" bar="bar"></clickable>
<hr /> {{ hello }} <button ng-click="setHello()">Change hello</button>
</body>
</html>
app = angular.module('app', []); app.controller('MainCtrl', function($scope) {
$scope.foo = 0;
$scope.bar = 0; $scope.hello = "Hello"; $scope.setHello = function() {
$scope.hello = "World";
};
}); app.directive('clickable', function() { return {
restrict: "E",
scope: {
foo: '=',
bar: '='
},
template: '<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.foo++;
scope.bar++;
});
}
} });
如果你点击上面蓝色区域,你是不会看到任何变化的,但是之后再点击button的话,
你就会突然看到0的数字变成一个数字,这个数字就是刚刚你点击了蓝色区域多少下的数字
就像刚刚我上面说的那样,指令里的click并没有触发$digest loop,但是当你点击button按钮的时候
按钮button上的ng-click就会调用$apply,然后就会执行$digest loop,
所有的$watch就会检查有没有改变(其中包含了foo和bar这两个$watch)
这时,你可能会想,这样的结果不是你想要的,你想要你点击那个指令块的时候马上就能看到变化。
其实这很简单,只需要调用的时候包含$apply就可以了
element.bind('click', function() {
scope.foo++;
scope.bar++; scope.$apply();
});
$apply是$scope(或者是指令link function上的scope)上的一个函数function,所以调用它会强制执行$digest loop
注意,如果已经有一个轮询loop了,在这种情况下调用它将抛出一个异常,这是一个迹象表明,我们不需要再调用$apply了。
其实上面的用法更好的是这样使用
element.bind('click', function() {
scope.$apply(function() {
scope.foo++;
scope.bar++;
});
})
这两种用法有什么区别呢?
区别是第一种用法中,我们更新新的值是在angular context外部,所以当有错误的时候,Angular是不会知道的。
显然在上面的小例子中它不会产生多大影响,但是想像下当我们在复杂项目使用多种库,然后出错的时候,Angular是不会知道自己的错误
所以如果你想在在项目中使用 jQuery plugin,你要确保你有调用$apply来执行$degest loop来更新DOM的变化。
Conclusion
最后,我希望你看完就明白了 Angular中data-binding的工作原理,我猜你看完的第一印象会觉得dirty-checking是很慢的
实际上它是很快的,实际上只有页面上达到2000-3000 个$watch的时候,它才会出现性能上的延迟,但是我觉得你应该用不到那么多个。
注:本文基本取自于$watch How the $apply Runs a $digest
原文地址http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/
谈谈Angular关于$watch,$apply 以及 $digest的工作原理的更多相关文章
- 理解Angular中的$apply()以及$digest()
$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...
- 深入理解Angular中的$Apply()以及$Digest()
$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...
- --@angularjs--理解Angular中的$apply()以及$digest()
$apply() 和 $digest() 在 AngularJS 中是两个核心概念,但是有时候它们又让人困惑.而为了了解 AngularJS 的工作方式,首先需要了解 $apply() 和 $dige ...
- (网页)理解Angular中的$apply()以及$digest()
转自CSDN: 工作有问题上CSDN上转转. $apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$ ...
- (转) 理解Angular中的$apply()以及$digest()
原文地址:http://blog.csdn.net/dm_vincent/article/details/38705099 $apply()和$digest()在AngularJS中是两个核心概念,但 ...
- $apply()和$digest()——angular
$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...
- angular $apply()以及$digest()讲解
重点的东西放上面,说三遍: 记住的最重要的是ng是否能检测到你对于model的修改.如果它不能检测到,那么你就需要手动地调用$apply()! 记住的最重要的是ng是否能检测到你对于model的修改. ...
- 通俗理解angularjs中的$apply,$digest,$watch
<!DOCTYPE html> <html lang="zh-CN" ng-app="app"> <head> <me ...
- bind、call、apply的区别与实现原理
1.简单说一下bind.call.apply的区别 三者都是用于改变函数体内this的指向,但是bind与apply和call的最大的区别是:bind不会立即调用,而是返回一个新函数,称为绑定函数,其 ...
随机推荐
- bzoj3667: Rabin-Miller算法
Description Input 第一行:CAS,代表数据组数(不大于350),以下CAS行,每行一个数字,保证在64位长整形范围内,并且没有负数.你需要对于每个数字:第一,检验是否是质数,是质 ...
- SAR ADC简介
SAR型 (逐次逼近型) 摘要:逐次逼近寄存器型(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场.SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位.SAR架构允许高 ...
- 关于OJ的输入和输出(转)
ACM竞赛之输入输出以下内容来源于互联网.在ACM程序设计竞赛中,一道题目的所有测试数据是放在一个文本文件中,选手将一道题目的程序提交给评判系统运行,程序从该文件中读取测试数据,再把运行结果输出到另一 ...
- capitalize()在Python中含义
Python为string对象提供了转换大小写的方法:upper() 和 lower(). 还不止这些,Python还为我们提供了首字母大写,其余小写的capitalize()方法, 以及所有单词首字 ...
- [HDOJ 5212] [BestCoder Round#39] Code 【0.0】
题目链接:HDOJ - 5212 题目分析 首先的思路是,考虑每个数对最终答案的贡献. 那么我们就要求出:对于每个数,以它为 gcd 的数对有多少对. 显然,对于一个数 x ,以它为 gcd 的两个数 ...
- VCC,VDD,VEE,VSS,VPP 表示的意义
转自VCC,VDD,VEE,VSS,VPP 表示的意义 VCC,VDD,VEE,VSS,VPP 表示的意义 版本一: 简单说来,可以这样理解: 一.解释 VCC:C=circuit 表示电路的意思, ...
- 【HDU 1542】Atlantis 矩形面积并(线段树,扫描法)
[题目] Atlantis Problem Description There are several ancient Greek texts that contain descriptions of ...
- LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the contex异常的原因
eclipse里面启动tomcat报这个错误的原因是由于jar包冲突了,在我的项目里面引入了jar包,但是我的工程里面有这个jar包的两个工程,都被导入到同一个项目里面了,导致不知道该去用哪一个类,所 ...
- UVA 10034 Freckles 最小生成树
虽然是道普通的最小生成树题目,可还是中间出了不少问题,暴露的一个问题是不够细心,不够熟练.所以这篇博客就当记录一下bug吧. 代码一:kruskal #include<stdio.h> # ...
- [wikioi]石子归并
http://wikioi.com/problem/1048/ 区间型动态规划.参考PPT:http://wenku.baidu.com/view/73c1ded5b9f3f90f76c61bc4.h ...