谈谈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/ 

转载于:https://www.cnblogs.com/cunjieliu/p/4370441.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398369.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tachyon更名为 Alluxio,并发布1.0版本

详细参考 http://www.alluxio.org/releases/alluxio-1-0-0-release.html http://geek.csdn.net/news/detail/57243 http://www.alluxio.org/ Alluxio介绍 Alluxio 1.0版本&#xff0c;作为世界上首款以内存为中心的虚拟分布式存储系统&#xff0c;它能够统一数据访问并成为连接…

一幅长文细学MongoDB(四)——索引

4 索引 文章目录4 索引4.1 概述4.2 索引类型4.3 索引创建4.3 删除索引4.4 查看索引执行计划4.5 涵盖的查询4.1 概述 说明&#xff1a;索引支持在MongoDB中高效地查询。如果没有索引&#xff0c;MongoDB必须执行全集合扫描&#xff0c;即扫描集合中的每个文档&#xff0c;以选择…

【转】Android图片加载神器之Fresco-加载图片基础[详细图解Fresco的使用]

Fresco简单的使用—SimpleDraweeView 百学须先立志—学前须知&#xff1a; 在我们平时加载图片(不管是下载还是加载本地图片…..)的时候&#xff0c;我们经常会遇到这样一个需求&#xff0c;那就是当图片正在加载时应该呈现正在加载时的图像&#xff0c;当图片加载失败时应该呈…

对象映射工具AutoMapper介绍

AutoMapper是用来解决对象之间映射转换的类库。对于我们开发人员来说&#xff0c;写对象之间互相转换的代码是一件极其浪费生命的事情&#xff0c;AutoMapper能够帮助我们节省不少时间。 一. AutoMapper解决了什么问题? 要问AutoMapper解决了什么问题&#xff1f; 难道不是对象…

MindSpore安装教程【简洁易懂】

1 官网 MindSpore官网&#xff1a;MindSpore安装指南 2 关注社区 3 下载 查看自己python版本&#xff1a;使用python -V查看自己python版本 进入官网选择相应配置&#xff1a; 验证是否安装成功&#xff1a;python -c "import mindspore;mindspore.run_check()"&a…

一幅长文细学Vue(十三)——组合式中的生命周期

13 组合式API&#xff08;四&#xff09; 摘要&#xff1a;每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被…

OSGI 生命周期

1 生命周期管理 对于非模块化应用&#xff0c;生命周期将应用作为一个整体来操作&#xff1b;而对于模块化应用&#xff0c;则可以以细粒度的方式来管理应用的某一个独立部分。OSGi生命周期管理 OSGi生命周期层有两种不同的作用&#xff1a; 在应用程序外部&#xff0c;定义了对…

tomcat+nginx+redis实现均衡负载、session共享

在项目运营时&#xff0c;我们都会遇到一个问题&#xff0c;项目需要更新时&#xff0c;我们可能需先暂时关闭下服务器来更新。但这可能会出现一些状况:1.用户还在操作&#xff0c;被强迫终止了(我们可以看日志等没人操作的时候更新&#xff0c;但总可能会有万一)2.不知道的用户…

洛谷 P3184 [USACO16DEC]Counting Haybales数草垛

洛谷 P3184 [USACO16DEC]Counting Haybales数草垛 题目描述 Farmer John has just arranged his NN haybales (1 \leq N \leq 100,0001≤N≤100,000 ) at various points along the one-dimensional road running across his farm. To make sure they are spaced out appropria…

Entity Framework 6 Recipes 2nd Edition(13-2)译 - 用实体键获取一个单独的实体

问题 不管你用DBFirst,ModelFirst或是CodeFirst的方式,你想用实体键获取一个单独的实体.在本例中,我们用CodeFirst的方式. 解决方案 假设你有一个模型表示一个Painting(绘画)类型的实体,如Figure 13-2所示: Figure 13-2. The Painting entity type in our model 在代码In Listi…

C#心得与经验(二)

本周学到很多C#关于Interface, Array的知识&#xff0c;在这里简单复习一下几个易混的地方&#xff0c;重在理解。 一、Interface 使用as来避免多态时没有接口的Exception&#xff1a; Document [] folder new Document[5]; for (int i 0; i < 5; i) {if (i % 2 0){fold…

项目总结(3.28)

项目是用vuewebpackelementUI 完成的。虽然没有什么深奥的技术和难点&#xff0c;但是有些细节还是值得注意的。 1、满足不同屏幕尺寸下缩放全屏显示。 单单只靠宽度、高度百分比是不可以实现的&#xff0c;比如如果宽度设置百分比&#xff0c;当屏幕宽度比较小时&#xff0c;这…

algorand共识协议_【Filecoin】理解预期共识 - 及它的优缺点

摘 要预期共识就是上帝掷飞镖预期共识的优点在于简单&#xff0c;而且每一次选举胜出者数量的平均数为1但预期共识不能保证每次选举的胜出者数量&#xff0c;这是其最大的问题期待有更好的基于可验证随机函数的共识算法出现&#xff0c;设计者可获得20万美金奖赏预期共识 就是 …

c++ 多个线程操作socket要同步吗_基础知识深化:NIO优化原理和Tomcat线程模型

1、I/O阻塞书上说BIO、NIO等都属于I/O模型&#xff0c;但是I/O模型这个范围有点含糊&#xff0c;我为此走了不少弯路。我们日常开发过程中涉及到NIO模型应用&#xff0c;如Tomcat、Netty中等线程模型&#xff0c;可以直接将其视为 网络I/O模型 。本文还是在基础篇章中介绍几种I…

Linux常用命令汇总--ln

1.功能&#xff1a;将一个文件或者文件夹链接到另外一个文件或者文件夹上。链接分为硬链接和软链接&#xff0c;硬链接可以看做是一个文件具有多个访问的入口&#xff0c;软链接可以看成是快捷方式。2.用法&#xff1a;ln [选项] 源文件或目录 目标文件或目录3.参数&#xff1a…

用同一uuid作为两个字段的值_这两个小技巧,让SQL语句不仅躲了坑,还提升了 1000 倍...

作者&#xff1a;帅地个人简介&#xff1a;一个热爱编程的在校生&#xff0c;我的世界不只有coding&#xff0c;还有writing。目前维护订阅号「苦逼的码农」&#xff0c;专注于写「算法与数据结构」&#xff0c;「Java」,「计算机网络」。本次来讲解与 SQL 查询有关的两个小知识…

Android SQLite详解

在项目开发中&#xff0c;我们或多或少都会用到数据库。在Android中&#xff0c;我们一般使用SQLite&#xff0c;因为Android在android.database.sqlite包封装了很多SQLite操作的API。我自己写了一个Demo来总结SQLite的使用&#xff0c;托管在Github上&#xff0c;大家可以点击…

Catalan数的理解

Catalan数的理解 f(0)1f(1)1f(2)2f(3)5f(4)14f(5)42f(2)f(1)f(1)f(3)f(2)f(1)*f(1)*f(2)f(4)f(3)f(2)*f(1)f(1)*f(2)f(3)通项公式&#xff1a;f(n) f(n-1) f(n-2)f(1) f(n-3)f(2) ... f(1)f(n-2) f(n-1) 理解&#xff1a;固定一个&#xff0c;n-1个全在左边&#xff0c;n-…

Type interface mapper.UserMapper is not known to the MapperRegistry

Type interface mapper.UserMapper is not known to the MapperRegistry. 报错&#xff1a;Type interface mapper.UserMapper is not known to the MapperRegistry. 解决&#xff1a;已经解决&#xff1b;请查看mapper是否配置正确&#xff0c;我下面就配置错误了。 解决效果…

微信整人假红包图片_警惕:千万别点!这些红包是假的

春节将至&#xff0c;又到了“考验手速”的时候。近年春节&#xff0c;“抢红包”为大家带来了“新年味”与许多快乐。但是&#xff0c;有些不法分子却从中捣乱&#xff0c;制造了一些假红包企图骗取钱财。如何辨别“假红包”&#xff1f;这里总结了几种“假红包”类型&#xf…