angularJS $watch $apply $digest
看O'Reilly的书看到$watch这部分,不过没看懂,网上很多资料也含糊不清,不过还是找到了几个好的,简单记录一下。
一句话说明,$watch是用来监视变量的,好了直接上代码
<html>
<head>
<script src='./js/angular.min.js'></script>
</head>
<body ng-app='watch'>
<input ng-model='name' type='text'/>
<div>change count: {{count}}</div>
<script>
var app = angular.module('watch',[]);
app.run(['$rootScope',function($rootScope){
$rootScope.count = 0;
$rootScope.name = 'Alfred';
$rootScope.$watch('name',function(){
$rootScope.count++;
})
}]);
</script>
</body>
</html>
上面这个代码就是用来监控name的变化的,每次当我们在input输入框中输入一个值的时候,$rootScope中的count就会对应的+1。
在angularJS的内部,每当我们对name的值进行修改的时候,angularJS内部中的$digest就会被调用一次,并在运行结束之后检查我们用$watch来监控的模型,如何和上一次执行$digest之前相比发生变化了,则执行$watch中的回调函数。
然而!!!在我们实际的开发中,仅仅实现对一个原始类型的数据监控是远远不能够满足所需的,对于原始类型的数据,如果我们使用了一次赋值操作,则这个原始类型的数据变量会真正的赋值一次,然而对于引用类型的变量,进行赋值时,仅仅是将赋值的变量指向了这个引用类型。
<html>
<head>
<script src='./js/angular.min.js'></script>
</head>
<body ng-app='watch'>
<div ng-repeat='item in items'>
<input ng-model='item.a'/><span>{{item.a}}</span>
</div>
<div>change count: {{count}}</div>
<script>
var app = angular.module('watch',[])
app.run(['$rootScope',function($rootScope){
$rootScope.count = 0;
$rootScope.items = [
{ "a": 1 },
{ "a": 2 },
{ "a": 3 },
{ "a": 4 }
];
$rootScope.$watch('items',function(){
$rootScope.count++;
});
}]);
</script>
</body>
</html>
在这个栗子中我们就会发现,不管我们怎么改变其中的值,count都不会发生变化的。而这个就是我们上面说那样,在说明这个之前,我们在说明下$watch的第三个参数,一般$watch函数的前两个参数是必传的(监控对象,回调函数),第三个参数默认为false,这样的话我们进行的监控叫做引用监控,这个意思就是监控对象的应用没有发生变化的时候就不算对象发生了变化,具体的来说,上面的例子,就算items的属性发生了变化,只要items的引用没有发生变化,$watch就都当做没有看见,但是比如讲一个数组赋值给items时,这个时候$watch就看不下去了。
相反,如果第三个参数设置为true的时候,那么我们的监控叫做“全等监控”,此时的$watch的要求就是比较苛刻了,只要他监控的对象有一点点变化时,$watch就会跳出来,卧槽!你居然还敢动!!!
当然值得提一下的是:为什么第三个参数加个true,这么方便了我们还不加呢?!当然是牵涉到性能的问题啦!全等监控运行起来的时候是先监控到整个对象,然后在每一次把$digest跑起来之前先用angualr.copy()将整个对象先拷贝之后再调用angular.equal()方法来进行比较,所以这一监控可能会消耗大量的资源!
在angularJS 1.1.4又出来了一个$watchCollection()方法,专门来监控数组集合的,他的性能介于引用监控和全等监控之间,它不会对数组的每一项内容 进行监控,而是当数组的pop和push时候做出反应。
$apply
$apply的作用是把改变同步绑定到界面上,但是它为什么存在呢?什么时候需要用它呢?什么时候又不需要呢?
那么我们首先说一下angular是如何进行数据双向绑定的吧。
要知道一个变量变了,方法无非就两种
1、 通过固定的接口,比如set,get方法,通过set设置变量的值,set被调用时做个比较就可以,但是这个方法和复杂!
2、 脏检查,将某一个对象复制一份快照,在某个时间,比较现在对象与快照的值。很明显,这个方法要复制两份对象,而且要遍历对象,比较每一个属性。对!这样的确有性能问题!但是angular就是用这个的~
但是人家angular的脏检查不是对所有对象进行检查,只是当对象绑定到html中,该对象才复合检查对象(watcher),同理,angular对属性的脏检查也是如此。
watcher源码
watcher = {
fn: listener, //监听回调函数
last: initWatchVal, //上一状态值
get: get, //取得监听的值
exp: watchExp, //监听表达式
eq: !!objectEquality //要不要比较引用
};
那么我们什么时候去进行脏检查呢?
脏检查的点是在函数执行完之后,但是不标明异步调用也执行完毕,如果我们的功能是异步的,那么我们会发现我们的改变并没有更新到DOM上。
举个栗子:
function Ctrl($scope) {
$scope.message = "Waiting 2000ms for update";
setTimeout(function () {
$scope.message = "Timeout called!"; // AngularJS unaware of update to $scope }, 2000);
},2000);
}
DOM上永远都不会显示Timeout called
当然,这个就是我们$apply的应用场景了,调用它,手动触发脏检查,举个例子:angularJs提供了$timeout,为什么咱有了setTimeout还要提供这个呢?就是应为$timeout异步完成后,angularJs会自动触发$apply。
注:应该尽可能地把要执行的代码和函数传递给$apply去执行,而不要自已执行那些函数然后再调用$apply。
例如
$scope.$apply(function() {
$scope.variable1 = 'some value';
executeSomeAction();
});
尽可能别这样
$scope.variable1 = 'some value';
executeSomeAction();
$scope.$apply();
$digest
然而这个脏检查有事怎么检查的呢??
$apply被调用后最终都会触发$digest()
在调用了$scope.$digest()后,$digest循环就开始了。
假设你在一个ng-click指令对应的handler函数中更改了scope中的一条数据,此时AngularJS会自动地通过调用$digest()来触发一轮$digest循环。
当$digest循环开始后,它会触发每个watcher。这些watchers会检查scope中的当前model值是否和上一次计算得到的model值不同。
如果不同,那么对应的回调函数会执行。
调用该函数的结果,就是view中的表达式内容会被更新。
除了ng-click指令,还有一些其它的built-in指令以及服务来让你更改models(比如ng-model,$timeout 等 ) 和自动触发一次 $digest 循环。
$apply补充
HTML:
<body ng-app="myApp">
<div ng-controller="MessageController">
Delayed Message: {{message}}
</div>
</body>
JavaScript:
angular.module('myApp',[]).controller('MessageController', function($scope) { $scope.getMessage = function() {
setTimeout(function() {
$scope.message = 'Fetched after 2 seconds';
console.log('message:'+$scope.message);
}, 2000);
} $scope.getMessage();
});
通过运行这个例子,你会看到过了两秒钟之后,控制台确实会显示出已经更新的model,然而,view并没有更新。原因也许你已经知道了,就是我们忘了调用$apply()方法。因此,我们需要修改getMessage(),如下所示:
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处理。
angularJS $watch $apply $digest的更多相关文章
- AngularJS中的digest循环$apply
欢迎大家指导与讨论 : ) 前言 Angular会拓展这个标准的浏览器流程,创建一个Angular上下文.这个Angular上下文指的是运行在Angular事件循环内的特定代码,该Angular事件循 ...
- $watch、$digest、$apply
$watch.$digest.$apply $watch 代表的就是对数据源的监听,当数据源发生变化,就会触发第二个参数的回调函数 $digest 代表触发一个数据源变化的事件 $apply 代表对于 ...
- angularJS $watch $digest $apply
一 简介AngularJS提供了一个非常酷的特性叫做双向数据绑定(Two-way Data Binding),这个特性大大简化了我们的代码编写方式.数据绑定意味着当View中有任何数据发生了变化,那么 ...
- angular的$watch,$digest和$apply
第一部分:$watch $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. $watch(watchExpression, listener, objectEqu ...
- Client-Side Template Injection with AngularJS
<html> <head> <meta charset="utf-8"> <script src="https://cdn.bo ...
- $digest / $apply digest in progress报错
有的时候出于某种原因,如jq操作了model.或者$watch.setTimeout等函数改变了model,导致最后没有脏数据检测.所以我没就手动调用了$apply( )等.但是第一次运行的时候ang ...
- Atitit HTTP 认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结
Atitit HTTP认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结 1.1. 最广泛使用的是基本验证 ( ...
- (转)构建自己的AngularJS,第一部分:Scope和Digest
原翻译链接:https://github.com/xufei/Make-Your-Own-AngularJS/edit/master/01.md 原文链接:http://teropa.info/blo ...
- 理解Angular中的$apply()以及$digest()
$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...
- 指令<AngularJs>
对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能. 首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法: angular.module('myApp', ...
随机推荐
- 24_IO_第24天(转换流、缓冲流)_讲义
今日内容介绍 1.转换流 2.缓冲流 01转换流概述 * A: 转换流概述 * a: 转换流概述 * OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流 ...
- cropper.js 超级好用的裁剪图片工具
最近要做一个照片裁剪功能.就选用了cropper.js 代码如下:贴出来 <div class="container"> <div class="row ...
- PHP 常用函数总结(二)
4.PHP处理数据库的常用函数. 汇总表 PHP 5 MySQLi 函数 函数 描述 mysqli_affected_rows() 返回前一个 Mysql 操作的受影响行数. mysqli_autoc ...
- windows多线程(四) 关键段 CriticalSection
一.问题回顾 我们上一篇文章最后的程序的输出 g_Count 的值不是每次都正确,原因是没有对全局资源 g_Count 进行互斥访问(就是同一时刻只能由一个线程访问),接下来我们就来说一下使用关键段来 ...
- mysql-master-ha 实现mysql master的高可用。
常用的mysql 高可用有下面几种方案: 名称 原理 特点 mysqlmha Perl脚本对mysql master做心跳,master down了以后,选举new master ,是要改代理层的 ...
- linux中创建和解压文档的 tar 命令教程
linux & zip & tar https://www.cnblogs.com/xgqfrms/p/9714161.html 1 linux中的tar命令 tar(磁带归档)命令是 ...
- BZOJ2878 NOI2012迷失游乐园(树形dp+环套树+概率期望)
考虑树的部分分怎么做.令f[i]为i向子树内走的期望路径长度,转移比较显然.算答案时先把其父亲的答案弄好就可以统计自己的答案了. 环套树也类似.树里直接dp,对环上点暴力考虑环上的每条路径,算完后再在 ...
- UVA12538 Version Controlled IDE
题意翻译 维护一种数据结构,资磁三种操作. 1.在p位置插入一个字符串s 2.从p位置开始删除长度为c的字符串 3.输出第v个历史版本中从p位置开始的长度为c的字符串 1≤n≤50000,所有字符串总 ...
- excel 技能收集
排序: 对某D列第4行开始的数据排序,对D4之后的700个数据全部排序 =RANK(D4,$D$4:$D$700)
- 【刷题】洛谷 P4209 学习小组
题目描述 共有n个学生,m个学习小组,每个学生只愿意参加其中的一些学习小组,且一个学生最多参加k个学习小组.每个学生参加学习小组财务处都收一定的手续费,不同的学习小组有不同的手续费.若有a个学生参加第 ...