Dirty Checking (脏值检查)

Digest cycle and $scope

Digest cycle and $scope

First and foremost, AngularJS defines a concept of a so-called digest cycle. This cycle can be considered as a loop, during which AngularJS checks if there are any changes to all the variables watched(被检测的变量) by all the $scopes. So if you have $scope.myVar defined in your controller and this variable was marked for being watched(被表明此变量需要被检测), then you are implicitly(隐式的表明需要被检测) telling AngularJS to monitor the changes on myVar in each iteration of the loop.

A natural follow-up question would be: Is everything attached to $scope being watched? Fortunately, no(并不是所有添加到$scope上面的东西都会被watch). If you would watch for changes to every object in your $scope, then quickly a digest loop would take ages to evaluate and you would quickly run into performance issues.

That is why the AngularJS team gave us two ways of declaring some $scope variable as being watched (read below).

$watch helps to listen for $scope changes

There are two ways of declaring a $scope variable as being watched(两种方法去告诉Angular某变量是否被标记需要被检测).

  1. By using it in your template via the expression <span>{{myVar}}</span>
  2. By adding it manually via the $watch service(手动的通过$watch服务)

Ad 1) This is the most common scenario and I'm sure you've seen it before, but you didn't know that this has created a watch in the background(在watch list 中添加了一个watch). Yes, it had! Using AngularJS directives (such as ng-repeat) can also create implicit watches.

Ad 2) This is how you create your own watches$watch service helps you to run some code when some value attached to the $scope has changed. It is rarely used, but sometimes is helpful. For instance, if you want to run some code each time 'myVar' changes, you could do the following:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
alert('hey, myVar has changed!');
}); $scope.buttonClicked = function() {
$scope.myVar = 2; // This will trigger $watch expression to kick in
};
}

$apply enables to integrate changes with the digest cycle

You can think of the $apply function as of an integration mechanism. You see, each time you change some watched variable attached to the $scope object directly, AngularJS will know that the change has happened. This is because AngularJS already knew to monitor those changes. So if it happens in code managed by the framework, the digest cycle will carry on.

However, sometimes you want to change some value outside of the AngularJS world and see the changes propagate normally. Consider this - you have a $scope.myVar value which will be modified within a jQuery's $.ajax() handler. This will happen at some point in future. AngularJS can't wait for this to happen, since it hasn't been instructed to wait on jQuery.

To tackle this, $apply has been introduced. It lets you to start the digestion cycle explicitly(显式地). However, you should only use this to migrate some data to AngularJS (integration with other frameworks(和其他的框架中的数据进行整合)), but never use this method combined with regular AngularJS code, as AngularJS will throw an error then.

How is all of this related to the DOM?

Well, you should really follow the tutorial again, now that you know all this. The digest cycle will make sure that the UI and the JavaScript code stays synchronised(视图和数据模型保持同步), by evaluating every watcher attached to the all $scopes as long as nothing changes. If no more changes happen in the digest loop, then it's considered to be finished.

You can attach objects to the $scope object either explicitly in the Controller, or by declaring them in {{expression}} form directly in the view.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

$watch How the $apply Runs a $digest

$watch How the $apply Runs a $digest  理解$watch ,$apply 和 $digest --- 理解数据绑定过程(译文)

The browser events-loop and the Angular.js extension

Our browser is waiting for events, for example the user interactions. If you click on a button or write into an input, the event’s callback will run inside Javascript and there you can do any DOM manipulation, so when the callback is done, the browser will make the appropiate changes in the DOM.

Angular extends this events-loop creating something called angular context(Angular上下文) (remember this, it is an important concept). To explain what this context is and how it works we will need to explain more concepts.

The $watch list

Every time you bind something in the UI you insert a $watch in a $watch list. Imagine the $watch as something that is able to detect changes in the model it is watching (bear with me, this will be clear soon). Imagine you have this:

index.html

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Here we have $scope.user, which is bound to the first input, and we have $scope.pass, which is bound to the second one. Doing this we add two $watch to the $watch list.

controllers.js

1
2
3
4
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});

index.html

1
Hello, {{ World }}

Here, even though we have two things attached to the $scope, only one is bound. So in this case we only created one $watch.

controllers.js

1
2
3
app.controller('MainCtrl', function($scope) {
$scope.people = [...];
});

index.html

1
2
3
4
5
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>

How many $watch are created here? Two for each person (for name and age) in people plus one for the ng-repeat. If we have 10 people in the list it will be (2 * 10) + 1, AKA 21 $watch.

So, everything that is bound in our UI using directives creates a $watch.(数据模型和视图绑定在一起才会产生一个$watch) Right, but when are those $watch created?

When our template is loaded, AKA in the linking phase, the compiler(在link阶段,compiler会注册register) will look for every directive and creates all the $watch that are needed. This sounds good, but… now what?

$digest-----------------$digest loop

Remember the extended event-loop I talked about? When the browser receives an event that can be managed by the angular context the $digest loop will be fired.

This loop is made from two smaller loops. One processes the $evalAsync queue and the other one processes the $watch list, which is the subject of this article.

What is that process about? The $digest will loop through the list of $watch that we have, asking this:

  • Hey $watch, what is your value?

    • It is 9
  • Alright, has it changed?
    • No, sir.
  • (nothing happens with this one, so it moves to the next)
  • You, what is your value?
    • It is Foo.
  • Has it changed?
    • Yes, it was Bar.
  • (good, we have a DOM to be updated)
  • This continues until every $watch has been checked(继续下一步,直到所有watch都被检查过).

This is the dirty-checking. Now that all the $watch have been checked there is something else to ask: Is there any $watch that has been updated? If there is at least one of them that has changed, the loop will fire again until all of the $watch report no changes(直到所有的$watch表示已经没有变化,停止脏值检查). This is to ensure that every model is clean. Have in mind that if the loop runs more than 10 times, it will throw an exception to prevent infinite loops.

When the $digest loop finishes(脏值检查循环结束之后,dom就会变化), the DOM makes the changes.

Example:

controllers.js

1
2
3
4
5
6
7
app.controller('MainCtrl', function() {
$scope.name = "Foo"; $scope.changeFoo = function() {
$scope.name = "Bar";
}
});

index.html

1
2
{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Here we have only one $watch because ng-click doesn’t create any watches (the function is not going to change(函数本身不会变) :P).

  • We press the button.
  • The browser receives an event which will enter the angular context (I will explain why, later in this article).
  • The $digest loop will run and will ask every $watch for changes.
  • Since the $watch which was watching for changes in $scope.name reports a change, if will force another $digest loop.
  • The new loop reports nothing.
  • The browser gets the control back and it will update the DOM reflecting the new value of $scope.name

The important thing here (which is seen as a pain-point by many people) is that EVERY event that enters the angular context will run a $digest loop. That means that every time we write a letter in an input, the loop will run checking every $watch in this page.

$apply-----------------Entering the angular context with $apply

angularJS之$apply()方法

$apply() 做的一件事,就是调用根作用域 $rootScope 的$digest()

$apply() is used to execute an expression in angular from outside of the angular framework(Angular框架外的Js代码). (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.

What says which events enter the angular context and which ones do not? $apply

If you call $apply when an event is fired, it will go through the angular-context, but if you don’t call it, it will run outside it. It is as easy as that. So you may now ask… That last example does work and I haven’t called $apply, why? Angular will do it for you(对于内置的指令,angular会为你调用$apply). So if you click on an element with ng-click, the event will be wrapped inside an $apply call. If you have an input with ng-model="foo" and you write an f, the event will be called like this: $apply("foo = 'f';"), in other words, wrapped in an $apply call.

When angular doesn’t use $apply for us

This is the common pain-point for newcomers to Angular. Why is my jQuery not updating my bindings? Because jQuery doesn’t call $apply and then the events never enter the angular context and then the $digest loop is never fired.

Let’s see an interesting example:

Imagine we have the following directive and controller:

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.directive('clickable', function() {

return {
restrict: "E",
scope: {
foo: '=',
bar: '='
},
template: '<ul style="<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;
});

It binds foo and bar from the controller to show them in a list, then every time we click on the element, both foo and bar values are incremented by one.

What will happen if we click on the element? Are we going to see the updates? The answer is no. No, because the click event is a common event that is not wrapped into an $apply call. So that means that we are going to lose our count? No.

What is happening is that the $scope is indeed changing but since that is not forcing a $digest loop, the $watch for foo and the one for bar are not running, so they are not aware of the changes. This also means that if we do something else that does run an $apply, then all the $watch we have will see that they have changed and then update the DOM as needed.

没有进入Angular Context,因此,count会累加,但是在页面上没有反映出来,毕竟还是JS,对变量的操作还是会生效

So if you want to use a jQuery plugin, be sure you call $apply if you need to run a $digest loopto update your DOM.

Something I want to add is that some people “feel bad” having to call $apply because they think that they are doing something wrong. That is not true. It is just Angular that is not a magician and it doesn’t know when a 3rd party library wants to update the bindings(第三方的库对数据模型的草错,Angular.js不会知道).

$watch -----------------------Using $watch for our own stuff

$watch

You already know that every binding we set has its own $watch to update the DOM when is needed, but what if we want our own watches for our purposes? Easy.

Let’s see some examples:

Example 1

app.js

1
2
3
4
5
6
7
8
9
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular"; $scope.updated = -1; $scope.$watch('name', function() {
$scope.updated++;
});
});

index.html

1
2
3
4
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>

That is how we create a new $watch. The first parameter can be a string or a function. In this case it is just a string with the name of what we want to $watch, in this case, $scope.name (notice how we just need to use name). The second parameter is what is going to happen when $watch says that our watched expression has changed. The first thing we have to know is that when the controller is executed and finds the $watch, it will immediately fire.

Example 2:

app.js

1
2
3
4
5
6
7
8
9
10
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular"; $scope.updated = 0; $scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
});

index.html

1
2
3
4
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>

The second parameter of $watch receives two parameters. The new value and the old value. We can use them to skip the first run that every $watch does. Normally you don’t need to skip the first run, but in the rare cases where you need it (like this one), this trick comes in handy.

Example 3:

app.js

1
2
3
4
5
6
7
8
9
10
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" }; $scope.updated = 0; $scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});

index.html

1
2
3
4
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>

We want to $watch any changes in our $scope.user object. Same as before but using an object instead of a primitive.

Uhm? It doesn’t work. Why? Because the $watch by default compares the reference of the objects(按引用). In example 1 and 2, every time we modify $scope.name it will create a new primitive, so the $watch will fire because the reference of the object is new and that is our change. In this new case, since we are watching $scope.user and then we are changing $scope.user.name, the reference of $scope.user is never changing because we are creating a new $scope.user.name every time we change the input, but the $scope.user will be always the same.

That is obviously not the desired case in this example.

Example 4:

app.js

1
2
3
4
5
6
7
8
9
10
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" }; $scope.updated = 0; $scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
});

index.html

1
2
3
4
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>

Now it is working! How? We added a third parameter to the $watch which is a bool to indicate that we want to compare the value of the objects instead of the reference. And since the value of $scope.user is changing when we update the $scope.user.name the $watch will fire appropriately.

There are more tips & tricks with $watch but these are the basics.

Understanding Angular’s $apply() and $digest()

Understanding Angular’s $apply() and $digest()

$apply() and $digest() are two core, and sometimes confusing, aspects of AngularJS. To understand how AngularJS works one needs to fully understand how $apply() and $digest() work. This article aims to explain what $apply()and $digest() really are, and how they can be useful in your day-to-day AngularJS programming.

$apply and $digest Explored

AngularJS offers an incredibly awesome feature known as two way data binding which greatly simplifies our lives. Data binding means that when you change something in the view, the scope model automagically updates. Similarly, whenever the scope model changes, the view updates itself with the new value. How does does AngularJS do that? When you write an expression ({{aModel}}), behind the scenes Angular sets up a watcher on the scope model, which in turn updates the view whenever the model changes. This watcher is just like any watcher you set up in AngularJS:

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

The second argument passed to $watch() is known as a listener function, and is called whenever the value of aModelchanges. It is easy for us to grasp that when the value of aModel changes this listener is called, updating the expression in HTML. But, there is still one big question! How does Angular figure out when to call this listener function? In other words, how does AngularJS know when aModel changes so it can call the corresponding listener? Does it run a function periodically to check whether the value of the scope model has changed? Well, this is where the $digest cycle steps in.

It’s the $digest cycle where the watchers are fired. When a watcher is fired, AngularJS evaluates the scope model, and if it has changed then the corresponding listener function is called. So, our next question is when and how this $digestcycle starts.

The $digest cycle starts as a result of a call to $scope.$digest(). Assume that you change a scope model in a handler function through the ng-click directive. In that case AngularJS automatically triggers a $digest cycle by calling$digest(). When the $digest cycle starts, it fires each of the watchers. These watchers check if the current value of thescope model is different from last calculated value. If yes, then the corresponding listener function executes. As a result if you have any expressions in the view they will be updated. In addition to ng-click, there are several other built-in directives/services that let you change models (e.g. ng-model$timeout, etc) and automatically trigger a $digest cycle.

So far, so good! But, there is a small gotcha. In the above cases, Angular doesn’t directly call $digest(). Instead, it calls $scope.$apply(), which in turn calls $rootScope.$digest(). As a result of this, a digest cycle starts at the $rootScope, and subsequently visits all the child scopes calling the watchers along the way.

Now, let’s assume you attach an ng-click directive to a button and pass a function name to it. When the button is clicked, AngularJS wraps the function call within $scope.$apply(). So, your function executes as usual, change models (if any), and a $digest cycle starts to ensure your changes are reflected in the view.

Note: $scope.$apply() automatically calls $rootScope.$digest().(自动触发) The $apply() function comes in two flavors(两种形式). The first one takes a function as an argument, evaluates it, and triggers a $digest cycle. The second version does not take any arguments and just starts a $digest cycle when called. We will see why the former one is the preferred approach shortly.

Angular1.x DirtyChecking(脏值检查) $watch, $apply, $digest的更多相关文章

  1. Angular DirtyChecking(脏值检查) $watch, $apply, $digest

    Dirty Checking (脏值检查) Digest cycle and $scope Digest cycle and $scope First and foremost, AngularJS ...

  2. AngularJS进阶(十六)脏值检查

    脏值检查 注:请点击此处进行充电! 需求 在项目开发过程中,需要对药店信息进行更改.如下图所示.现在的需求是:当药店信息没有发生变化时,点击"更新信息"按钮,提示"药店信 ...

  3. 迈向angularjs2系列(4):脏值检测机制

    目录 一: 概念简介 脏值检测,简单的说就是在MVC的构架中,视图会通过模型的change事件来更新自己. 脏值检测的核心代码是观察者模式的实现,其机制会执行digest循环,在特定UI组件的上下文执 ...

  4. AngularJs 脏值检查及其相关

    今天突然就想写写$digest和$apply,这些都是脏值检查的主体内容. 先以普通js来做一个简单的监控例子吧: var div = ducoment.getElementById("my ...

  5. Angularjs 脏值检测

    文章转自:http://www.ituring.com.cn/article/39865 构建自己的AngularJS,第一部分:Scope和Digest 原文链接:http://teropa.inf ...

  6. Angular - - 脏值检查及其相关

    今天突然就想写写$digest和$apply,这些都是脏值检查的主体内容. 先以普通js来做一个简单的监控例子吧: var div = ducoment.getElementById("my ...

  7. ionic3 双向数据绑定失效 脏值检测失效

    最近在使用ionic3过程中,使用了eval()方法进行字符串拼接成一个function使用 在eval()方法中,只能使用局部变量,全局变量无法使用,ionic3的this在eval中失效(unde ...

  8. angularjs 做不到实时脏值查询

    angularjs 做不到脏值查询 ,数据请求过来,不操作其他按钮,请求的值就是展示不出来:(相当于,只有手动触发,angularjs内部才会把脏值查询出来): 解决办法:在请求过来的值旁边加上$sc ...

  9. 脏数据清洗,pandas.apply()的应用

    原数据如下所示: IMAGETYPE count .?+? 1713 Jh.5? 100 .??U 38 .11.1 1 .13.1 1 .15.11 2 我需要对数据内的带有特殊符号,且第一个逗号 ...

随机推荐

  1. redis集群配置及运行命令(windows和centos)附Python测试范例代码

    表示配置主服务器器的IP和端口 slaveof <masterip> <masterport> # 设置slave是否是只读的.从2.6版起,slave默认是只读的. slav ...

  2. (转)AIX光盘备份与恢复

    AIX光盘备份与恢复 在此之前,说明一下光盘映像的格式UDF和ISO9660 ISO9660: 这是国际标准化组织(ISO)于1985年颁布的通用光盘文件系统.目前使用最广泛的光盘文件系统,能被所有的 ...

  3. 隐型马尔科夫模型(HMM) 简介

    先介绍一下马尔科夫模型: 马尔可夫模型(Markov Model)是一种统计模型,广泛应用在语音识别,词性自动标注,音字转换,概率文法等各个自然语言处理等应用领域.经过长期发展,尤其是在语音识别中的成 ...

  4. spark 中如何查看单个RDD分区的内容(创建分区,查看分区数)

    spark 创建分区 val scores = Array(("Fred", 88), ("Fred", 95), ("Fred", 91) ...

  5. phpdocumentor生成代码注释文档(linux)

    1,默认安装lnmp环境(php7),默认pear安装 2,   pear channel-discover pear.phpdoc.org pear install phpdoc/phpDocume ...

  6. SpringBoot学习之自动装配

    在前面使用SSM集成时,我们可以使用注解实现无配置化注入,但是这种依赖被进行“人工干预了的”,换句话就是说我们手动进行装配,那么此时还没有达到SpringBoot这种自动装配的效果,那么究竟Sprin ...

  7. Android RecyclerView的使用

    RecyclerView是什么? RecyclerView是一种新的视图组件,目标是为任何基于适配器的视图提供相似的渲染方式.它被作为ListView和GridView控件的继承者,在最新的suppo ...

  8. gles2.0环境的在windows上的建立

    这里也有一个视频来讲解,大家可以看下,可以多提问题,意见,建议 http://edu.csdn.net/course/detail/606 #include <Windows.h> #in ...

  9. RabbitMQ上手记录–part 5-节点集群高可用(多服务器)

    上一part<RabbitMQ上手记录–part 4-节点集群(单机多节点)>中介绍了RabbitMQ集群的一些概念以及实现了在单机上运行多个节点,并且将多个节点组成一个集群. 通常情况下 ...

  10. elasticsearch插件的开发--计算特征向量的相似度

    目录 更改elasticsearch的score评分 插件源码解读 脚步一 脚本二(fast-vector-distance) 部署 测试 创建索引 查询 版本说明 项目详细见github 参考文献 ...