How does AngularJS implement its two-way data bind

2019-02-07 02:43发布

问题:

AngularJS allows you to implement two-way data binding. However, the interesting part is how it detects model changes? The model is usually a plain object like the code below. We can change the name property of $scope.user but how does AngularJS detect the model changed? Does AngularJS enum all the properties of the $scope object?

angular.module('myApp', [])
      .controller('BusinessCardController', function($scope){
        $scope.user = {
          name: 'Tanay Pant'
        }
      });

<input type="text" ng-model="user.name" placeholder="Full Name" />

回答1:

There is a digest cycle, where the scope examines all of the $watch expressions and compares them with the previous value. It looks at the object models for changes, if the old value isn't the same as the new value, AngularJS will update the appropriate places, a.k.a dirty checking.

In order for the digest cycle to be execute $apply(fn) has to be run, this is how you enter the Angular world from JavaScript. How does $apply(fn) get called (taken from AngularJs integration with browser):

  1. The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server).
  2. The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure.
  3. Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes.

Data Binding

Digest Cycle Explanation

In order to achieve two-way binding, directives register watchers. For a page to be fast and efficient we need to try and reduce all these watchers that we create. So you should be careful when using two-way binding - i.e. only use it when you really needed. Otherwise use one-way:

<h1> {{ ::vm.title }} </h1>

Here it is quite obvious that the title of the page probably won't be changed while the user is on the page - or needs to see the new one if it is changed. So we can use :: to register a one-way binding during the template linking phase.

The main issues I've seen with explosions of watchers are grids with hundreds of rows. If these rows have quite a few columns and in each cell there is two-way data binding, then you're in for a treat. You can sit back and wait like in modem times for the page to load!



回答2:

Two-way binding is limited almost exclusively to elements that use ng-model. The direction going from view to model uses standard event handlers to detect changes that must be updated within the model (e.g., onchange). The direction going from the model back to the view is updated during a $digest. But we do not call $digest directly.

Every element that is on your page that is going to respond to the digest cycle will, somewhere, attach a listener and an expression to its scope using $watch. When you write {{ foo() }}, or when you use ng-model='user.name', internally there is a call to $watch made on your behalf with a Javascript expression that will be run every time a digest cycle is run. This registration might happen during the compile of the template (our first example), or it might happen during the link phase of a directive (our second).

There is no magic here. The listeners that are attached are regular functions -- in our example, the listener for the expression foo() is provided for you, and it will update the html text on the page, while the listener for the expression user.name will call setText, or setOption, or whatever is required by the particular input which ng-model has been attached.

While angular can handle most of the listening, you can attach your own watch expressions with your own listeners manually inside any function that has access to a scope (scope is important because we will tear down those watchers if the corresponding parts of the page are removed). Be mindful of excess. Bindings aren't free, and the more things that are bound, the slower the page will respond. One-time bindings are one way of reducing this cost. Using $on with $emit and $broadcast are another.

So when is digest called? It is certainly not automatic. If the digest cycle is running, it means someone somewhere called $apply on their scope or on the root scope. ng-model attaches handlers which will respond to regular html events and will make calls to $apply on your behalf. But foo(), on the other hand, will never get called until some other bit of script somewhere calls $apply. Fortunately, most functions that you fill out for angular wrap those functions with a call to $apply, so you don't often have to make the call yourself (e.g., $timeout is wrapped with $apply, which is why we use it instead of setTimeout). But if you were using something outside of the scope of angular (a 3rd party library that connects to events), you would need to remember to call $apply yourself, and just like above, you can do this manually by calling $apply anywhere you have access to a scope.



回答3:

In order to make Data Binding possible, AngularJS uses $watch API's to observer the changes on the scope. AngularJS registered watchers for each variable on the scope to observe the value in it. If the value of the variable on the scope gets changes, then the view gets updated automatically.

It happens because of the $digest cycle is triggered. Hence, AngularJS processes all the registered watchers on the current scope and the children and check for the updates and call the dedicated watcher listeners until the model is stabilized and no more listeners are fired. Once the $digest loop finishes the execution, the browser re-renders the DOM and reflects the changes

By default, every variable on a scope is observed by the angular. In this way, unnecessary variable are also observed by the angular that is time consuming and as a result page is becoming slow.