全面解析Angular中$Apply()及$Digest()的区别


Posted in Javascript onAugust 04, 2016

$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑。而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的。这篇文章旨在解释$apply()和$digest()是什么,以及在日常的编码中如何应用它们。

1、探索$apply()和$digest()

1.1、认识双向数据绑定和$watch();

AngularJS提供了一个非常酷的特性叫做双向数据绑定(Two-way Data Binding),这个特性大大简化了我们的代码编写方式。数据绑定意味着当View中有任何数据发生了变化,那么这个变化也会自动地反馈到scope的数据上,也即意味着scope模型会自动地更新。类似地,当scope模型发生变化时,view中的数据也会更新到最新的值。那么AngularJS是如何做到这一点的呢?当你写下表达式如{{ aModel }}时,AngularJS在幕后会为你在scope模型上设置一个watcher,它用来在数据发生变化的时候更新view。这里的watcher和你会在AngularJS中设置的watcher是一样的:

$scope.$watch(‘aModel', function(newValue, oldValue) { 
//update the DOM with newValue 
});

传入到$watch()中的第二个参数是一个回调函数,该函数在aModel的值发生变化的时候会被调用。当aModel发生变化的时候,这个回调函数会被调用来更新view这一点不难理解,但是,还存在一个很重要的问题!AngularJS是如何知道什么时候要调用这个回调函数呢?换句话说,AngularJS是如何知晓aModel发生了变化,才调用了对应的回调函数呢?它会周期性的运行一个函数来检查scope模型中的数据是否发生了变化吗?好吧,这就是$digest循环的用武之地了。

在$digest循环中,watchers会被触发。当一个watcher被触发时,AngularJS会检测scope模型,如何它发生了变化那么关联到该watcher的回调函数就会被调用。那么,下一个问题就是$digest循环是在什么时候以各种方式开始的?

在调用了$scope.$digest()后,$digest循环就开始了。假设你在一个ng-click指令对应的handler函数中更改了scope中的一条数据,此时AngularJS会自动地通过调用$digest()来触发一轮$digest循环。当$digest循环开始后,它会触发每个watcher。这些watchers会检查scope中的当前model值是否和上一次计算得到的model值不同。如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是view中的表达式内容(译注:诸如{{ aModel }})会被更新。除了ng-click指令,还有一些其它的built-in指令以及服务来让你更改models(比如ng-model,$timeout等)和自动触发一次$digest循环。

目前为止还不错!但是,有一个小问题。在上面的例子中,AngularJS并不直接调用$digest(),而是调用$scope.$apply(),后者会调用$rootScope.$digest()。因此,一轮$digest循环在$rootScope开始,随后会访问到所有的children scope中的watchers。

Note: $scope.$apply()会自动地调用$rootScope.$digest()。

$apply()方法有两种形式:

第一种会接受一个function作为参数,执行该function并且触发一轮$digest循环。

第二种会不接受任何参数,只是触发一轮$digest循环。我们马上会看到为什么第一种形式更好。

1.2、什么时候手动调用$apply()方法?

如果AngularJS总是将我们的代码wrap到一个function中并传入$apply(),以此来开始一轮$digest循环,那么什么时候才需要我们手动地调用$apply()方法呢?实际上,AngularJS对此有着非常明确的要求,就是它只负责对发生于AngularJS上下文环境中的变更会做出自动地响应(即,在$apply()方法中发生的对于models的更改)。AngularJS的built-in指令就是这样做的,所以任何的model变更都会被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通过手动调用$apply()来通知AngularJS。这就像告诉AngularJS,你修改了一些models,希望AngularJS帮你触发watchers来做出正确的响应。

比如,如果你使用了JavaScript中的setTimeout()来更新一个scope model,那么AngularJS就没有办法知道你更改了什么。这种情况下,调用$apply()就是你的责任了,通过调用它来触发一轮$digest循环。类似地,如果你有一个指令用来设置一个DOM事件listener并且在该listener中修改了一些models,那么你也需要通过手动调用$apply()来确保变更会被正确的反映到view中。

让我们来看一个例子。加入你有一个页面,一旦该页面加载完毕了,你希望在两秒钟之后显示一条信息。你的实现可能是下面这个样子的:

html:

<body ng-app=“myApp”> 
<div ng-controller=“MessageController”> 
Delayed Message: {{message}} 
</div> 
</body>

JavaScript:

/* What happens without an $apply() */ 
angular.module(‘myApp',[]).controller(‘MessageController', function($scope) { 
$scope.getMessage = function() { 
setTimeout(function() { 
$scope.message = ‘Fetched after 3 seconds'; 
console.log(‘message:'+$scope.message); 
}, 2000); 
} 
$scope.getMessage(); 
});

通过运行这个例子,你会看到过了两秒钟之后,控制台确实会显示出已经更新的model,然而,view并没有更新。原因也许你已经知道了,就是我们忘了调用$apply()方法。因此,我们需要修改getMessage(),如下所示:

/* What happens with $apply */ 
angular.module(‘myApp',[]).controller(‘MessageController', function($scope) { 
$scope.getMessage = function() { 
setTimeout(function() { 
$scope.$apply(function() { 
//wrapped this within $apply 
$scope.message = ‘Fetched after 3 seconds'; 
console.log(‘message:' + $scope.message); 
}); 
}, 2000); 
} 
$scope.getMessage(); 
});

如果你运行了上面的例子,你会看到view在两秒钟之后也会更新。唯一的变化是我们的代码现在被wrapped到了$scope.$apply()中,它会自动触发$rootScope.$digest(),从而让watchers被触发用以更新view。

Note:顺便提一下,你应该使用$timeout service来代替setTimeout(),因为前者会帮你调用$apply(),让你不需要手动地调用它。

而且,注意在以上的代码中你也可以在修改了model之后手动调用没有参数的$apply(),就像下面这样:

$scope.getMessage = function() { 
setTimeout(function() { 
$scope.message = ‘Fetched after two seconds'; 
console.log(‘message:' + $scope.message); 
$scope.$apply(); //this triggers a $digest 
}, 2000); 
};

以上的代码使用了$apply()的第二种形式,也就是没有参数的形式。需要记住的是你总是应该使用接受一个function作为参数的$apply()方法。这是因为当你传入一个function到$apply()中的时候,这个function会被包装到一个try…catch块中,所以一旦有异常发生,该异常会被$exceptionHandler service处理。

使用 $apply()的情况如下:

•通常可以依赖于Angular提供的可用于视图中的任意指令来调用 $apply() 。所有 ng-[event]指令(比如 ng-click 、 ng-keypress )都会调用 $apply() 。

•此外还可以依赖于一系列Angular内置的服务来调用 $digest() 。比如 $http 服务会在XHR请求完成并触发更新返回值(promise)之后调用 $apply() 。

•无论何时我们手动处理事件,使用第三方框架(比如jQuery、Facebook API) ,或者调用setTimeout() ,都可以使用 $apply() 函数让Angular返回 $digest 循环。

调用setTimeout():

<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>$scope.$apply()用法</title>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div id="div1" ng-controller="mytext"> 
<div>{{text}}</div> 
<input id="btn" type="button" value="jquery-event"></input> 
</div> 
</body>
</html>
<script type="text/javascript">
var myModule = angular.module('myApp', []); 
myModule.controller("mytext",function($scope){ 
$scope.text = "place"; 
setTimeout(function(){ 
$scope.text = "value setted after time out"; 
$scope.$apply();//必需手动进行脏值检测,否则数据无法刷新到界面 
},1000); 
}); 
</script>

使用第三方框架(比如jQuery、Facebook API):

<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>$scope.$apply()用法</title>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/jquery/3.1.0/jquery.min.js"></script>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div id="div1" ng-controller="mytext"> 
<div>{{text}}</div> 
<input id="btn" type="button" value="jquery-event"></input> 
</div> 
</body>
</html>
<script type="text/javascript">
var myModule = angular.module('myApp', []); 
myModule.controller("mytext",function($scope){ 
$scope.text = "place"; 
}); 
$(function(){ 
$("#btn").click(function(){ 
var $scope = $("#btn").scope(); 
$scope.text = "value setted in jquery"; 
$scope.$apply(); 
}); 
}) 
</script>

1.3、$digest循环会运行多少次?

当一个$digest循环运行时,watchers会被执行来检查scope中的models是否发生了变化。如果发生了变化,那么相应的listener函数就会被执行。这涉及到一个重要的问题。如果listener函数本身会修改一个scope model呢?AngularJS会怎么处理这种情况?

答案是$digest循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有models发生了变化。这就是脏检查(Dirty Checking),它用来处理在listener函数被执行时可能引起的model变化。因此,$digest循环会持续运行直到model不再发生变化,或者$digest循环的次数达到了10次。因此,尽可能地不要在listener函数中修改model。

Note: $digest循环最少也会运行两次,即使在listener函数中并没有改变任何model。正如上面讨论的那样,它会多运行一次来确保models没有变化。

结语

需要记住的最重要的是AngularJS是否能检测到你对于model的修改。如果它不能检测到,那么你就需要手动地调用$apply()。

如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Javascript SHA-1:Secure Hash Algorithm
Dec 20 Javascript
extjs grid取到数据而不显示的解决
Dec 29 Javascript
cookie 最近浏览记录(中文escape转码)具体实现
Jun 08 Javascript
jQuery控制的不同方向的滑动(向左、向右滑动等)
Jul 18 Javascript
node.js中的events.EventEmitter.listenerCount方法使用说明
Dec 08 Javascript
js+css实现导航效果实例
Feb 10 Javascript
浅谈js和css内联外联注意事项
Jun 30 Javascript
vue做网页开场视频的实例代码
Oct 20 Javascript
parabola.js抛物线与加入购物车效果的示例代码
Oct 25 Javascript
Vue中使用vee-validate表单验证的方法
May 09 Javascript
vue-cli3 项目从搭建优化到docker部署的方法
Jan 28 Javascript
vue项目前端知识点整理【收藏】
May 13 Javascript
window.open不被拦截的简单实现代码(推荐)
Aug 04 #Javascript
js实现多图左右切换功能
Aug 04 #Javascript
window.open打开窗口被拦截的快速解决方法
Aug 04 #Javascript
浅谈js中子页面父页面方法 变量相互调用
Aug 04 #Javascript
JS图片等比例缩放方法完整示例
Aug 03 #Javascript
JS获取url参数、主域名的方法实例分析
Aug 03 #Javascript
JavaScript中的冒泡排序法
Aug 03 #Javascript
You might like
php数据结构 算法(PHP描述) 简单选择排序 simple selection sort
2011/08/09 PHP
解析用PHP实现var_export的详细介绍
2013/06/20 PHP
你可能不知道PHP get_meta_tags()函数
2014/05/12 PHP
PHP分享图片的生成方法
2018/04/25 PHP
ExtJS4 组件化编程,动态加载,面向对象,Direct
2011/05/12 Javascript
js弹出框轻量级插件jquery.boxy使用介绍
2013/01/15 Javascript
jQuery实现带动画效果的二级下拉导航方法
2015/03/11 Javascript
详谈javascript中的cookie
2015/06/03 Javascript
js实现带圆角的多级下拉菜单效果
2015/08/28 Javascript
javascript:void(0)是什么意思及href=#与href=javascriptvoid(0)的区别
2015/11/13 Javascript
js实现简单的验证码
2015/12/25 Javascript
JavaScript中子对象访问父对象的方式详解
2016/09/01 Javascript
利用JS判断字符串是否含有数字与特殊字符的方法小结
2016/11/25 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
2017/01/04 Javascript
koa2 数据api中间件设计模型的实现方法
2020/07/13 Javascript
[51:00]Secret vs VGJ.S 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
[45:25]完美世界DOTA2联赛循环赛 PXG vs IO 第一场 11.06
2020/11/09 DOTA
Python IDLE清空窗口的实例
2018/06/25 Python
Python txt文件加入字典并查询的方法
2019/01/15 Python
Python数据可视化库seaborn的使用总结
2019/01/15 Python
python GUI库图形界面开发之PyQt5访问系统剪切板QClipboard类详细使用方法与实例
2020/02/27 Python
Django在Model保存前记录日志实例
2020/05/14 Python
利用Python实现Excel的文件间的数据匹配功能
2020/06/16 Python
美国儿童珠宝在线零售商:Loveivy
2019/05/22 全球购物
服务标兵事迹材料
2014/05/04 职场文书
七夕活动策划方案
2014/08/16 职场文书
2014年营业员工作总结
2014/11/18 职场文书
社区三八妇女节活动总结
2015/02/06 职场文书
2015年安全生产工作总结范文
2015/04/02 职场文书
运动会5000米加油稿
2015/07/21 职场文书
同学聚会感言一句话
2015/07/30 职场文书
导游词之京东大峡谷旅游区
2019/10/29 职场文书
php远程请求CURL案例(爬虫、保存登录状态)
2021/04/01 PHP
CSS3实现的文字弹出特效
2021/04/16 HTML / CSS
Python+Appium实现自动抢微信红包
2021/05/21 Python
Docker官方工具docker-registry案例演示
2022/04/13 Servers