看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的更多相关文章

  1. AngularJS中的digest循环$apply

    欢迎大家指导与讨论 : ) 前言 Angular会拓展这个标准的浏览器流程,创建一个Angular上下文.这个Angular上下文指的是运行在Angular事件循环内的特定代码,该Angular事件循 ...

  2. $watch、$digest、$apply

    $watch.$digest.$apply $watch 代表的就是对数据源的监听,当数据源发生变化,就会触发第二个参数的回调函数 $digest 代表触发一个数据源变化的事件 $apply 代表对于 ...

  3. angularJS $watch $digest $apply

    一 简介AngularJS提供了一个非常酷的特性叫做双向数据绑定(Two-way Data Binding),这个特性大大简化了我们的代码编写方式.数据绑定意味着当View中有任何数据发生了变化,那么 ...

  4. angular的$watch,$digest和$apply

    第一部分:$watch $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. $watch(watchExpression, listener, objectEqu ...

  5. Client-Side Template Injection with AngularJS

    <html> <head> <meta charset="utf-8"> <script src="https://cdn.bo ...

  6. $digest / $apply digest in progress报错

    有的时候出于某种原因,如jq操作了model.或者$watch.setTimeout等函数改变了model,导致最后没有脏数据检测.所以我没就手动调用了$apply( )等.但是第一次运行的时候ang ...

  7. Atitit HTTP 认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结

    Atitit HTTP认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结 1.1. 最广泛使用的是基本验证 ( ...

  8. (转)构建自己的AngularJS,第一部分:Scope和Digest

    原翻译链接:https://github.com/xufei/Make-Your-Own-AngularJS/edit/master/01.md 原文链接:http://teropa.info/blo ...

  9. 理解Angular中的$apply()以及$digest()

    $apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...

  10. 指令<AngularJs>

    对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能. 首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法: angular.module('myApp', ...

随机推荐

  1. 12_Java面向对象_第12天(构造方法、this、super)_讲义

    今日内容介绍 1.构造方法 2.this关键字 3.super关键字 4.综合案例 01构造方法引入 A:构造方法的引入 在开发中经常需要在创建对象的同时明确对象的属性值, 比如员工入职公司就要明确他 ...

  2. sql中详解round(),floor(),ceiling()函数的用法和区别?

    round() 遵循四舍五入把原值转化为指定小数位数,如:round(1.45,0) = 1;round(1.55,0)=2floor()向下舍入为指定小数位数 如:floor(1.45,0)= 1; ...

  3. jion()说明

    转自:http://www.cnblogs.com/skywang12345/p/3479275.html jion(),只有当子线程执行完了,主线程才会执行 1. join()介绍 join() 定 ...

  4. Java容器深入浅出之HashSet、TreeSet和EnumSet

    Java集合中的Set接口,定义的是一类无顺序的.不可重复的对象集合.如果尝试添加相同的元素,add()方法会返回false,同时添加失败.Set接口包括3个主要的实现类:HashSet.TreeSe ...

  5. .net对Redis集群的调用(FrameWork版本4.0)

    使用 StackExchange.Redis 实现.net对Redis集群的调用,方法如下: 1.打开 项目-管理NuGet程序包 2.在打开界面搜索StackExchange.Redis,找到Sta ...

  6. Delphi报的错误

    引入单元时提示Unit 'Unit1' already uses all the units in the project. 可能是没有添加环境变量造成的,需要手动输入代码引用单元. 和Environ ...

  7. 栈java实现

    这几天,过得挺充实的,每天都在不停的上课,早上很早就起来去跑步,晚上到图书馆看书.一边紧张的学习,一边在默默的备战软考.最近还接手了一个公司官网的建设.这是我在川信最后的一个完整学期了,每件事我都要认 ...

  8. Fair CodeForces - 987D(巧妙bfs)

    题意: 有n个城市 m条边,每条边的权值为1,每个城市生产一种商品(可以相同,一共k种),求出分别从每个城市出发获得s种商品时所走过路的最小权值 解析: 我们倒过来想,不用城市找商品,而是商品找城市, ...

  9. 【hihocoder编程练习赛9】闰秒

    题目链接 #include<stdio.h> #include<string.h> #include<algorithm> #include<math.h&g ...

  10. CF600E Lomsat gelral 【线段树合并】

    题目链接 CF600E 题解 容易想到就是线段树合并,维护每个权值区间出现的最大值以及最大值位置之和即可 对于每个节点合并一下两个子节点的信息 要注意叶子节点信息的合并和非叶节点信息的合并是不一样的 ...