


Detailed explanation of the data binding process of $watch, $apply and $digest
This time I will bring you $watch, $apply and $digestData bindingDetailed explanation of the process, Notes on the data binding process of $watch, $apply and $digest What are they? Here are actual cases. Let’s take a look.
This blog post is mainly written for novices, for those who have just started to contact Angular and want to understand how data binding works. If you already know a lot about Angular, it is strongly recommended that you go directly to read the source code. Angular users all want to know how data binding is implemented. You may see various words: $watch, $apply, $digest, dirty-checking, what are they? How do they work? Here I want to answer these questions. In fact, they have been answered in the official documents, but I still want to combine them together, but I just use a simple method to explain. If you want to understand the technical details, View source code.
Browser Event Loop and Angular.js Extensions
Our browser is always waiting for events, such as user interaction. If you click a button or enter something in the input box, the callback function of the event will be executed in the javascript interpreter, and then you can do any DOM operation and wait for the callback function When execution is completed, the browser will make changes to the DOM accordingly. Angular extends this event loop to generate an execution environment that is sometimes called the angular context (remember, this is an important concept). In order to explain what context is and how it works, we need to explain a few more concepts. $watch queue ($watch list) Every time you bind something to your UI you insert a $watch into the $watch queue. Imagine that $watch is something that can detect changes in the model it monitors. For example, you have the following code
index.html
User: <input type="text" ng-model="user" />Password: <input type="password" ng-model="pass" />
, which is bound to the second input box, and then we add two $watch:# to the $watch list: ##
controllers.js app.controller('MainCtrl', function($scope) { $scope.foo = "Foo"; $scope.world = "World";}); index.html Hello, {{ World }}
controllers.js app.controller('MainCtrl', function($scope) { $scope.people = [...];}); index.html <ul> <li ng-repeat="person in people"> {{person.name}} - {{person.age}} </li></ul>
. Therefore, every data bound to the UI will generate a $watch. Yes, when was $watch
generated? When our template is loaded, that is, in the linking phase (Angular is divided into compile phase and linking phase---Translator's Note), the Angular interpreter will look for each directive and then generate each required $watch. Sounds good, but what next?
Remember the expanded event loop I mentioned earlier? The $digest loop is triggered when the browser receives an event that can be handled by the angular context. This loop is composed of two smaller loops. One handles the evalAsync queue and the other handles the $watch queue, which is also the subject of this blog post. What does this deal with? $digest will iterate over our $watch and ask:
- Okay, has it changed? No, sir.
- (This variable has not changed, then the next one)
What about you, what is your value?
- Report, it's Foo.
-Have you changed anything just now? Changed, just now it was Bar.
(Very good, we have DOM that needs to be updated)
Continue to ask until the $watch
queue has been checked.
app.controller('MainCtrl', function() { $scope.name = "Foo"; $scope.changeFoo = function() { $scope.name = "Bar"; }}); index.html {{ name }}<button ng-click="changeFoo()">Change the name</button>
这里我们有一个$watch因为ng-click不生成$watch(函数是不会变的)。我们按下按钮浏览器接收到一个事件,进入angular context(后面会解释为什么)。
$digest循环开始执行,查询每个$watch是否变化。由于监视$scope.name的$watch
报告了变化,它会强制再执行一次$digest循环。新的$digest循环没有检测到变化。
浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。
这里很重要的(也是许多人的很蛋疼的地方)是每一个进入angular context
的事件都会执行一个$digest
循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$watch。
通过$apply来进入angular context
谁决定什么事件进入angular context,而哪些又不进入呢?$apply!
如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用$apply啊,为什么?Angular为了做了!因此你点击带有ng-click的元素时,时间就会被封装到一个$apply调用。如果你有一个ng-model="foo"的输入框,然后你敲一个f,事件就会这样调用$apply("foo = 'f';")
。
Angular什么时候不会自动为我们$apply呢?
这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply,事件没有进入angular context,$digest循环永远没有执行。
我们来看一个有趣的例子:假设我们有下面这个directive和controller
app.js
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
从controller里绑定到一个list里面,每次点击这个元素的时候,foo和bar
都会自增1。那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到$apply里面的常见的事件,这意味着我们会失去我们的计数吗?不会真正的结果是:$scope确实改变了,但是没有强制$digest
循环,监视foo 和bar的$watch没有执行。也就是说如果我们自己执行一次$apply
那么这些$watch就会看见这些变化,然后根据需要更新DOM。
试试看吧:http://jsbin.com/opimat/2/
如果我们点击这个directive(蓝色区域),我们看不到任何变化,但是我们点击按钮时,点击数就更新了。如刚才说的,在这个directive上点击时我们不会触发$digest
循环,但是当按钮被点击时,ng-click会调用$apply,然后就会执行$digest循环,于是所有的$watch都会被检查,当然就包括我们的foo和bar的$watch了。
现在你在想那并不是你想要的,你想要的是点击蓝色区域的时候就更新点击数。很简单,执行一下$apply就可以了:
element.bind('click', function() { scope.foo++; scope.bar++; scope.$apply();});
$apply是我们的$scope(或者是direcvie里的link函数中的scope)的一个函数,调用它会强制一次$digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行$apply的标志)。
试试看:http://jsbin.com/opimat/3/edit
有用啦!但是有一种更好的使用$apply的方法:
element.bind('click', function() { scope.$apply(function() { scope.foo++; scope.bar++; });})
有什么不一样的?差别就是在第一个版本中,我们是在angular context
的外面更新的数据,如果有发生错误,Angular永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个alert框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进$apply里面,Angular永远不会知道失败了,alert框就永远不会弹出来了。
因此,如果你想使用一个jQuery插件,并且要执行$digest循环来更新你的DOM的话,要确保你调用了$apply。
有时候我想多说一句的是有些人在不得不调用$apply时会“感觉不妙”,因为他们会觉得他们做错了什么。其实不是这样的,Angular不是什么魔术师,他也不知道第三方库想要更新绑定的数据。使用$watch来监视你自己的东西你已经知道了我们设置的任何绑定都有一个它自己的$watch,当需要时更新DOM,但是我们如果要自定义自己的watches呢?简单来看个例子:
app.js
app.controller('MainCtrl', function($scope) { $scope.name = "Angular"; $scope.updated = -1; $scope.$watch('name', function() { $scope.updated++; });});
index.html
<body ng-controller="MainCtrl"> <input ng-model="name" /> Name updated: {{updated}} times.</body>
这就是我们创造一个新的$watch的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,在这里,$scope.name
(注意我们只需要用name)。第二个参数是当$watch说我监视的表达式发生变化后要执行的。我们要知道的第一件事就是当controller执行到这个$watch
时,它会立即执行一次,因此我们设置updated为-1。
试试看:http://jsbin.com/ucaxan/1/edit
例子2:
app.js
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
<body ng-controller="MainCtrl"> <input ng-model="name" /> Name updated: {{updated}} times.</body>
watch的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。灵活点嘛少年。
例子3:
app.js
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
<body ng-controller="MainCtrl"> <input ng-model="user.name" /> Name updated: {{updated}} times.</body>
我们想要监视$scope.user对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。
试试看:http://jsbin.com/ucaxan/3/edit
呃?没用,为啥?因为$watch
默认是比较两个对象所引用的是否相同,在例子1和2里面,每次更改$scope.name
都会创建一个新的基本变量,因此$watch会执行,因为对这个变量的引用已经改变了。在上面的例子里,我们在监视$scope.user,当我们改变$scope.user.name
时,对$scope.user的引用是不会改变的,我们只是每次创建了一个新的$scope.user.name,但是$scope.user永远是一样的。
例子4:
app.js
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
<body ng-controller="MainCtrl"> <input ng-model="user.name" /> Name updated: {{updated}} times.</body>
试试看:http://jsbin.com/ucaxan/4/edit
现在有用了吧!因为我们对$watch加入了第三个参数,它是一个bool类型的参数,表示的是我们比较的是对象的值而不是引用。由于当我们更新$scope.user.name
时$scope.user也会改变,所以能够正确触发。
关于$watch还有很多tips&tricks,但是这些都是基础。
总结
好吧,我希望你们已经学会了在Angular中数据绑定是如何工作的。我猜想你的第一印象是dirty-checking很慢,好吧,其实是不对的。它像闪电般快。但是,是的,如果你在一个模版里有2000-3000个watch,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了
无论如何,随着ECMAScript6的到来,在Angular未来的版本里我们将会有Object.observe那样会极大改善$digest循环的速度。同时未来的文章也会涉及一些tips&tricks。
另一方面,这个主题并不容易,如果你发现我落下了什么重要的东西或者有什么东西完全错了,请指正(原文是在GITHUB上PR 或报告issue
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
The above is the detailed content of Detailed explanation of the data binding process of $watch, $apply and $digest. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

You may have encountered the problem of green lines appearing on the screen of your smartphone. Even if you have never seen it, you must have seen related pictures on the Internet. So, have you ever encountered a situation where the smart watch screen turns white? On April 2, CNMO learned from foreign media that a Reddit user shared a picture on the social platform, showing the screen of the Samsung Watch series smart watches turning white. The user wrote: "I was charging when I left, and when I came back, it was like this. I tried to restart, but the screen was still like this during the restart process." Samsung Watch smart watch screen turned white. The Reddit user did not specify the smart watch. Specific model. However, judging from the picture, it should be Samsung Watch5. Previously, another Reddit user also reported

We all know that the function of the listener is to trigger every time the reactive state changes. In the combined API, we can use the watch() function and watchEffect() function. When you change the reactive state, it may be triggered at the same time. Trigger Vue component updates and listener callbacks. By default, user-created listener callbacks will be called before the Vue component is updated. This means that the DOM you access in the listener callback will be the state it was in before it was updated by Vue. So, let’s take a look, how can we make good use of them? What's the difference between them? The watch() function watch needs to listen to a specific data source, such as listening to a ref. The first parameter of watch can be

How to use watch in Vue to monitor array changes. Vue is one of the most widely used frameworks in front-end development. It provides many convenient ways to implement functions such as data responsiveness, template rendering, and componentization. In Vue, we often use watch to monitor data changes. However, when we need to monitor array changes, we need to pay attention to some details. In Vue, we can use watch to monitor changes in a single property or object. The basic usage is as follows: watch:{

How to Access Control Center in watchOS 10 The way we interact with our watches has remained more or less the same since Apple launched the first Apple Watch. Even after adding so many new features, the overall user interface remains consistent. But watchOS10 brings big changes! On an Apple Watch running watchOS 9 or earlier, you can quickly open Control Center by swiping up on the screen. However, with the update to watchOS 10, the swipe-up gesture pulls up a whole new smart stack of widgets instead of Control Center. So the big question is how to open the Control Center on Apple Watch in WatchOS10. The answer is as follows:

Data preparation: In order to aggregate the conditions of the query, several pieces of data are added. MIN we try to get the minimum age. Method implementation @OverridepublicIntegergetAgeMin(){Mapresult=testFluentMybatisMapper.findOneMap(newTestFluentMybatisQuery().select.min.age("minAge").end()).orElse(null);returnresult!=null?Convert.toInt(result.get (&qu

The essence of watch The essence of watch is to observe a responsive data and notify and execute the corresponding callback function when the data changes. In fact, the essence of watch implementation is to use the effect and options.scheduler options. As shown in the following example: //The watch function receives two parameters, source is the responsive data, and cb is the callback function functionwatch(source,cb){effect(//Trigger the read operation to establish the connection ()=>source.foo ,{scheduler(){//When the data changes, call the callback function cbcb()}})} as shown in the above code

Vue component communication: using watch and computed for data monitoring Vue.js is a popular JavaScript framework, and its core idea is componentization. In a Vue application, data needs to be transferred and communicated between different components. In this article, we will introduce how to use Vue's watch and computed to monitor and respond to data. watch In Vue, watch is an option, which can be used to monitor the changes of one or more properties.

What you'll need: A new Apple Watch band A clean, soft cloth A flat, well-lit workspace Steps to change your Apple Watch band: 1. Remove the current band: Before you begin, place your Apple Watch face down on a clean, soft cloth to prevent the dial from being scratched. Press and hold the strap release button located on the back of the watch. The pusher is a small oval, flush with the watch body, where the two strap components join. While holding this button, slide the band over to remove it. You may need to apply a little force, but it should slide out relatively easily. Remember to remove both parts of the band. 2. Match the new band to your Apple Watch: Before installing the new band, make sure it matches your
