全面解析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 学习笔记(十二) dom
Jan 21 Javascript
用jquery与css打造个性化的单选框和复选框
Oct 20 Javascript
JS返回上一页实例代码通过图片和按钮分别实现
Aug 16 Javascript
jquery获取元素值的方法(常见的表单元素)
Nov 15 Javascript
真正好用的js验证上传文件大小的简单方法
Oct 27 Javascript
原生js实现新闻列表展开/收起全文功能
Jan 20 Javascript
jQuery中复合选择器简单用法示例
Mar 31 jQuery
layui 给数据表格加序号的方法
Aug 20 Javascript
在vue项目中正确使用iconfont的方法
Sep 28 Javascript
VUE中使用MUI方法
Feb 12 Javascript
10个最受欢迎的 JavaScript框架(推荐)
Apr 24 Javascript
JavaScript实现网页动态生成表格
Nov 25 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下图片文字混合水印与缩略图实现代码
2009/12/11 PHP
PHP获取MySql新增记录ID值的3种方法
2014/06/24 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
php从memcache读取数据再批量写入mysql的方法
2014/12/29 PHP
浅谈php的TS和NTS的区别
2019/03/13 PHP
ThinkPHP5.1表单令牌Token失效问题的解决
2019/03/22 PHP
php redis setnx分布式锁简单原理解析
2020/10/23 PHP
JQuery中判断一个元素下面是否有内容或者有某个标签的判断代码
2012/02/02 Javascript
Extjs4中的分页应用结合前后台
2013/12/13 Javascript
浅析document.ready和window.onload的区别讲解
2013/12/18 Javascript
javascript打开word文档的方法
2014/04/16 Javascript
Javascript学习笔记之相等符号与严格相等符号
2014/11/23 Javascript
Javascript数组中push方法用法分析
2016/10/31 Javascript
JavaScript中双向数据绑定详解
2017/05/03 Javascript
微信小程序实现根据字母选择城市功能
2017/08/16 Javascript
Vue2.0基于vue-cli+webpack父子组件通信(实例讲解)
2017/09/14 Javascript
web前端开发中常见的多列布局解决方案整理(一定要看)
2017/10/15 Javascript
详解JS数值Number类型
2018/02/07 Javascript
nodejs实现一个word文档解析器思路详解
2018/08/14 NodeJs
Node.js之readline模块的使用详解
2019/03/25 Javascript
DWR内存兼容及无法调用问题解决方案
2020/10/16 Javascript
举例讲解Python设计模式编程的代理模式与抽象工厂模式
2016/01/16 Python
编写Python爬虫抓取豆瓣电影TOP100及用户头像的方法
2016/01/20 Python
深入浅析Python获取对象信息的函数type()、isinstance()、dir()
2018/09/17 Python
HTML5移动端开发中的Viewport标签及相关CSS用法解析
2016/04/15 HTML / CSS
Tory Burch美国官方网站:美国时尚生活品牌
2016/08/01 全球购物
Lime Crime官网:美国一家主打梦幻精灵系的彩妆品牌
2019/03/22 全球购物
Java的类与C++的类有什么不同
2014/01/18 面试题
个人找工作求职简历的自我评价
2013/10/20 职场文书
管理科学大学生求职信
2013/11/13 职场文书
《谁的本领大》教后反思
2014/04/25 职场文书
关于旅游的活动方案
2014/08/15 职场文书
领导个人查摆剖析材料
2014/10/29 职场文书
2015年安全教育月活动总结
2015/03/26 职场文书
学校后勤工作总结2015
2015/05/15 职场文书
2016师德师风学习心得体会
2016/01/12 职场文书