This article is mainly written for novices, for those who have just started to get in touch with 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.
Let’s start from the beginning.
Browser Event Loop and Angular.js Extensions
Our browser is always waiting for events, such as user interactions. 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. When the callback function completes, the browser will operate on the DOM accordingly. Make changes. 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 will 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" />
Here we have $scope.user, which is bound to the first input box, and $scope.pass, which It is bound to the second input box, and then we add two $watches to the $watch list
Then look at the following example:
controllers.js
app.controller('MainCtrl', function($scope) { $scope.foo = "Foo"; $scope.world = "World"; });
index.html
Hello, {{ World }}
Here, even though we added two things to $scope, only one is bound to the UI, so only one $watch is generated here.
Look at the following example:
controllers. js
app.controller('MainCtrl', function($scope) { $scope.people = [...]; });
index.html
<ul> <li ng-repeat="person in people"> {{person.name}} - {{person.age}} </li> </ul>
How many $watches are generated here? Each person has two (one name, one age), and then one ng-repeat, so the total of 10 persons is (2 * 10) + 1, which means there are 21 $watches. 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 generate each required $watch. Sounds good, but what next?
$digest loop
Remember the extended 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:
Hey $watch, what’s your value?
It’s 9.
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, it was Bar just now.
(Good, we have DOM that needs to be updated)
Continue to ask until the $watch queue has been checked.
This is called dirty-checking. Now that all $watches have been checked, we have to ask: Has $watch been updated? If at least one has been updated, the loop will be triggered again until all $watches are unchanged. This ensures that each model will not change again. Remember that if the loop exceeds 10 times, it will throw an exception to prevent infinite looping. When the $digest loop ends, the DOM changes accordingly.
For example: controllers.js
app.controller('MainCtrl', function() { $scope.name = "Foo"; $scope.changeFoo = function() { $scope.name = "Bar"; } });
index.html
{{ name }} <button ng-click="changeFoo()">Change the name</button>
Here we have a $watch because ng-click does not generate $watch (the function will not change).
We press the button
The browser receives an event and enters the angular context (I will explain why later).
The $digest loop begins to execute, querying whether each $watch changes.
Since the $watch monitoring $scope.name reports a change, it will force another $digest cycle.
New $digest loop detects no changes.
The browser takes back control and updates the DOM corresponding to the new value of $scope.name.
A very important thing here (and a pain point for many people) is that every event that enters the angular context will execute a $digest loop, which means that every time we enter a letter, the loop will check all $watches on the entire page. .
Enter angular context through $apply
Who decides which events enter angular context and which do not? $apply!
如果當(dāng)事件觸發(fā)時(shí),你調(diào)用$apply,它會(huì)進(jìn)入angular context,如果沒(méi)有調(diào)用就不會(huì)進(jìn)入?,F(xiàn)在你可能會(huì)問(wèn):剛才的例子里我也沒(méi)有調(diào)用$apply啊,為什么?Angular為了做了!因此你點(diǎn)擊帶有ng-click的元素時(shí),時(shí)間就會(huì)被封裝到一個(gè)$apply調(diào)用。如果你有一個(gè)ng-model="foo"的輸入框,然后你敲一個(gè)f,事件就會(huì)這樣調(diào)用$apply("foo = 'f';")。
Angular什么時(shí)候不會(huì)自動(dòng)為我們$apply呢?
這是Angular新手共同的痛處。為什么我的jQuery不會(huì)更新我綁定的東西呢?因?yàn)閖Query沒(méi)有調(diào)用$apply,事件沒(méi)有進(jìn)入angular context,$digest循環(huán)永遠(yuǎn)沒(méi)有執(zhí)行。
我們來(lái)看一個(gè)有趣的例子:
假設(shè)我們有下面這個(gè)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里綁定到一個(gè)list里面,每次點(diǎn)擊這個(gè)元素的時(shí)候,foo和bar都會(huì)自增1。
那我們點(diǎn)擊元素的時(shí)候會(huì)發(fā)生什么呢?我們能看到更新嗎?答案是否定的。因?yàn)辄c(diǎn)擊事件是一個(gè)沒(méi)有封裝到$apply里面的常見(jiàn)的事件,這意味著我們會(huì)失去我們的計(jì)數(shù)嗎?不會(huì)
真正的結(jié)果是:$scope確實(shí)改變了,但是沒(méi)有強(qiáng)制$digest循環(huán),監(jiān)視foo 和bar的$watch沒(méi)有執(zhí)行。也就是說(shuō)如果我們自己執(zhí)行一次$apply那么這些$watch就會(huì)看見(jiàn)這些變化,然后根據(jù)需要更新DOM。
試試看吧:http://jsbin.com/opimat/2/
如果我們點(diǎn)擊這個(gè)directive(藍(lán)色區(qū)域),我們看不到任何變化,但是我們點(diǎn)擊按鈕時(shí),點(diǎn)擊數(shù)就更新了。如剛才說(shuō)的,在這個(gè)directive上點(diǎn)擊時(shí)我們不會(huì)觸發(fā)$digest循環(huán),但是當(dāng)按鈕被點(diǎn)擊時(shí),ng-click會(huì)調(diào)用$apply,然后就會(huì)執(zhí)行$digest循環(huán),于是所有的$watch都會(huì)被檢查,當(dāng)然就包括我們的foo和bar的$watch了。
現(xiàn)在你在想那并不是你想要的,你想要的是點(diǎn)擊藍(lán)色區(qū)域的時(shí)候就更新點(diǎn)擊數(shù)。很簡(jiǎn)單,執(zhí)行一下$apply就可以了:
element.bind('click', function() { scope.foo++; scope.bar++; scope.$apply(); });
$apply是我們的$scope(或者是direcvie里的link函數(shù)中的scope)的一個(gè)函數(shù),調(diào)用它會(huì)強(qiáng)制一次$digest循環(huán)(除非當(dāng)前正在執(zhí)行循環(huán),這種情況下會(huì)拋出一個(gè)異常,這是我們不需要在那里執(zhí)行$apply的標(biāo)志)。
試試看:http://jsbin.com/opimat/3/edit
有用啦!但是有一種更好的使用$apply的方法:
element.bind('click', function() { scope.$apply(function() { scope.foo++; scope.bar++; }); })
有什么不一樣的?差別就是在第一個(gè)版本中,我們是在angular context的外面更新的數(shù)據(jù),如果有發(fā)生錯(cuò)誤,Angular永遠(yuǎn)不知道。很明顯在這個(gè)像個(gè)小玩具的例子里面不會(huì)出什么大錯(cuò),但是想象一下我們?nèi)绻袀€(gè)alert框顯示錯(cuò)誤給用戶,然后我們有個(gè)第三方的庫(kù)進(jìn)行一個(gè)網(wǎng)絡(luò)調(diào)用然后失敗了,如果我們不把它封裝進(jìn)$apply里面,Angular永遠(yuǎn)不會(huì)知道失敗了,alert框就永遠(yuǎn)不會(huì)彈出來(lái)了。
因此,如果你想使用一個(gè)jQuery插件,并且要執(zhí)行$digest循環(huán)來(lái)更新你的DOM的話,要確保你調(diào)用了$apply。
有時(shí)候我想多說(shuō)一句的是有些人在不得不調(diào)用$apply時(shí)會(huì)“感覺(jué)不妙”,因?yàn)樗麄儠?huì)覺(jué)得他們做錯(cuò)了什么。其實(shí)不是這樣的,Angular不是什么魔術(shù)師,他也不知道第三方庫(kù)想要更新綁定的數(shù)據(jù)。
使用$watch來(lái)監(jiān)視你自己的東西
你已經(jīng)知道了我們?cè)O(shè)置的任何綁定都有一個(gè)它自己的$watch,當(dāng)需要時(shí)更新DOM,但是我們?nèi)绻远x自己的watches呢?簡(jiǎn)單
來(lái)看個(gè)例子:
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>
這就是我們創(chuàng)造一個(gè)新的$watch的方法。第一個(gè)參數(shù)是一個(gè)字符串或者函數(shù),在這里是只是一個(gè)字符串,就是我們要監(jiān)視的變量的名字,在這里,$scope.name(注意我們只需要用name)。第二個(gè)參數(shù)是當(dāng)$watch說(shuō)我監(jiān)視的表達(dá)式發(fā)生變化后要執(zhí)行的。我們要知道的第一件事就是當(dāng)controller執(zhí)行到這個(gè)$watch時(shí),它會(huì)立即執(zhí)行一次,因此我們?cè)O(shè)置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的第二個(gè)參數(shù)接受兩個(gè)參數(shù),新值和舊值。我們可以用他們來(lái)略過(guò)第一次的執(zhí)行。通常你不需要略過(guò)第一次執(zhí)行,但在這個(gè)例子里面你是需要的。靈活點(diǎn)嘛少年。
例子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>
我們想要監(jiān)視$scope.user對(duì)象里的任何變化,和以前一樣這里只是用一個(gè)對(duì)象來(lái)代替前面的字符串。
試試看:http://jsbin.com/ucaxan/3/edit
呃?沒(méi)用,為啥?因?yàn)?watch默認(rèn)是比較兩個(gè)對(duì)象所引用的是否相同,在例子1和2里面,每次更改$scope.name都會(huì)創(chuàng)建一個(gè)新的基本變量,因此$watch會(huì)執(zhí)行,因?yàn)閷?duì)這個(gè)變量的引用已經(jīng)改變了。在上面的例子里,我們?cè)诒O(jiān)視$scope.user,當(dāng)我們改變$scope.user.name時(shí),對(duì)$scope.user的引用是不會(huì)改變的,我們只是每次創(chuàng)建了一個(gè)新的$scope.user.name,但是$scope.user永遠(yuǎn)是一樣的。
例子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
現(xiàn)在有用了吧!因?yàn)槲覀儗?duì)$watch加入了第三個(gè)參數(shù),它是一個(gè)bool類型的參數(shù),表示的是我們比較的是對(duì)象的值而不是引用。由于當(dāng)我們更新$scope.user.name時(shí)$scope.user也會(huì)改變,所以能夠正確觸發(fā)。
關(guān)于$watch還有很多tips&tricks,但是這些都是基礎(chǔ)。
總結(jié)
好吧,我希望你們已經(jīng)學(xué)會(huì)了在Angular中數(shù)據(jù)綁定是如何工作的。我猜想你的第一印象是dirty-checking很慢,好吧,其實(shí)是不對(duì)的。它像閃電般快。但是,是的,如果你在一個(gè)模版里有2000-3000個(gè)watch,它會(huì)開(kāi)始變慢。但是我覺(jué)得如果你達(dá)到這個(gè)數(shù)量級(jí),就可以找個(gè)用戶體驗(yàn)專家咨詢一下了
無(wú)論如何,隨著ECMAScript6的到來(lái),在Angular未來(lái)的版本里我們將會(huì)有Object.observe那樣會(huì)極大改善$digest循環(huán)的速度。同時(shí)未來(lái)的文章也會(huì)涉及一些tips&tricks。
另一方面,這個(gè)主題并不容易,如果你發(fā)現(xiàn)我落下了什么重要的東西或者有什么東西完全錯(cuò)了,請(qǐng)指正(原文是在GITHUB上PR 或報(bào)告issue)
希望本文所述對(duì)大家AngularJS程序設(shè)計(jì)有所幫助。

Hot AI Tools

Undress AI Tool
Undress images for free

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

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

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

Javascript is a very unique language. It is unique in terms of the organization of the code, the programming paradigm of the code, and the object-oriented theory. The issue of whether Javascript is an object-oriented language that has been debated for a long time has obviously been There is an answer. However, even though Javascript has been dominant for twenty years, if you want to understand popular frameworks such as jQuery, Angularjs, and even React, just watch the "Black Horse Cloud Classroom JavaScript Advanced Framework Design Video Tutorial".

In today's information age, websites have become an important tool for people to obtain information and communicate. A responsive website can adapt to various devices and provide users with a high-quality experience, which has become a hot spot in modern website development. This article will introduce how to use PHP and AngularJS to build a responsive website to provide a high-quality user experience. Introduction to PHP PHP is an open source server-side programming language ideal for web development. PHP has many advantages, such as easy to learn, cross-platform, rich tool library, development efficiency

With the continuous development of the Internet, Web applications have become an important part of enterprise information construction and a necessary means of modernization work. In order to make web applications easy to develop, maintain and expand, developers need to choose a technical framework and programming language that suits their development needs. PHP and AngularJS are two very popular web development technologies. They are server-side and client-side solutions respectively. Their combined use can greatly improve the development efficiency and user experience of web applications. Advantages of PHPPHP

With the rapid development of Web technology, Single Page Web Application (SinglePage Application, SPA) has become an increasingly popular Web application model. Compared with traditional multi-page web applications, the biggest advantage of SPA is that the user experience is smoother, and the computing pressure on the server is also greatly reduced. In this article, we will introduce how to build a simple SPA using Flask and AngularJS. Flask is a lightweight Py

With the popularity and development of the Internet, front-end development has become more and more important. As front-end developers, we need to understand and master various development tools and technologies. Among them, PHP and AngularJS are two very useful and popular tools. In this article, we will explain how to use these two tools for front-end development. 1. Introduction to PHP PHP is a popular open source server-side scripting language. It is suitable for web development and can run on web servers and various operating systems. The advantages of PHP are simplicity, speed and convenience

The content of this article is about the basic introduction to AngularJS. It has certain reference value. Now I share it with you. Friends in need can refer to it.

With the popularity of web applications, the front-end framework AngularJS has become increasingly popular. AngularJS is a JavaScript framework developed by Google that helps you build web applications with dynamic web application capabilities. On the other hand, for backend programming, PHP is a very popular programming language. If you are using PHP for server-side programming, then using PHP with AngularJS will bring more dynamic effects to your website.

With the popularity of the Internet, more and more people are using the network to transfer and share files. However, due to various reasons, using traditional methods such as FTP for file management cannot meet the needs of modern users. Therefore, establishing an easy-to-use, efficient, and secure online file management platform has become a trend. The online file management platform introduced in this article is based on PHP and AngularJS. It can easily perform file upload, download, edit, delete and other operations, and provides a series of powerful functions, such as file sharing, search,
