AngularJS 学习笔记---Scope

前端之家收集整理的这篇文章主要介绍了AngularJS 学习笔记---Scope前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

What are Scopes?

Scopeis an object that refers to the application model. It is an execution context forexpressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watchexpressionsand propagate events.

Scope characteristics

  • Scopes provide APIs ($watch) to observe model mutations.

  • Scopes provide APIs ($apply) to propagate any model changes through the system into the view from outside of the "Angular realm" (controllers,services,Angular event handlers).

  • Scopes can be nested to limit access to the properties of application components while providing access to shared model properties. Nested scopes are either "child scopes" or "isolate scopes". A "child scope" (prototypically) inherits properties from its parent scope. An "isolate scope" does not. Seeisolated scopesfor more information.

  • Scopes provide context against whichexpressionsare evaluated. For example{{username}}expression is meaningless,unless it is evaluated against a specific scope which defines theusernameproperty.

Scope as Data-Model

Scope is the glue between application controller and the view. During the templatelinkingphase thedirectivesset up$watchexpressions on the scope. The$watchallows the directives to be notified of property changes,which allows the directive to render the updated value to the DOM.

Both controllers and directives have reference to the scope,but not to each other. This arrangement isolates the controller from the directive as well as from the DOM. This is an important point since it makes the controllers view agnostic,which greatly improves the testing story of the applications.

Edit in Plunker

script.jsindex.html

index.html

<div ng-controller="MyController">
  Your name:
    <input type="text" ng-model="username">
    <button ng-click='sayHello()'>greet</button>
  <hr>
  {{greeting}}
</div>

script.js

angular.module('scopeExample',[])
.controller('MyController',['$scope',function($scope) {
  $scope.username = 'World';

  $scope.sayHello = function() {
    $scope.greeting = 'Hello ' + $scope.username + '!';
  };
}]);

In the above example notice that theMyControllerassignsWorldto theusernameproperty of the scope. The scope then notifies theinputof the assignment,which then renders the input with username pre-filled. This demonstrates how a controller can write data into the scope.

Similarly the controller can assign behavior to scope as seen by thesayHellomethod,which is invoked when the user clicks on the 'greet' button. ThesayHellomethod can read theusernameproperty and create agreetingproperty. This demonstrates that the properties on scope update automatically when they are bound to HTML input widgets.

Logically the rendering of{{greeting}}involves:

  • retrieval of the scope associated with DOM node where{{greeting}}is defined in template. In this example this is the same scope as the scope which was passed intoMyController. (We will discuss scope hierarchies later.)

  • Evaluate thegreetingexpressionagainst the scope retrieved above,and assign the result to the text of the enclosing DOM element.

You can think of the scope and its properties as the data which is used to render the view. The scope is the single source-of-truth for all things view related.

From a testability point of view,the separation of the controller and the view is desirable,because it allows us to test the behavior without being distracted by the rendering details.

it('should say hello',function() {
  var scopeMock = {};
  var cntl = new MyController(scopeMock);

  // Assert that username is pre-filled
  expect(scopeMock.username).toEqual('World');

  // Assert that we read new username and greet
  scopeMock.username = 'angular';
  scopeMock.sayHello();
  expect(scopeMock.greeting).toEqual('Hello angular!');
});

Scope Hierarchies

Each Angular application has exactly oneroot scope,but may have several child scopes.

The application can have multiple scopes,because somedirectivescreate new child scopes (refer to directive documentation to see which directives create new scopes). When new scopes are created,they are added as children of their parent scope. This creates a tree structure which parallels the DOM where they're attached.

When Angular evaluates{{name}},it first looks at the scope associated with the given element for thenameproperty. If no such property is found,it searches the parent scope and so on until the root scope is reached. In JavaScript this behavior is known as prototypical inheritance,and child scopes prototypically inherit from their parents.

This example illustrates scopes in application,and prototypical inheritance of properties. The example is followed by a diagram depicting the scope boundaries.

Edit in Plunker

index.htmlscript.jsstyle.css

<div class="show-scope-demo">
  <div ng-controller="GreetController">
    Hello {{name}}!
  </div>
  <div ng-controller="ListController">
    <ol>
      <li ng-repeat="name in names">{{name}} from {{department}}</li>
    </ol>
  </div>
</div>

Notice that Angular automatically placesng-scopeclass on elements where scopes are attached. The<style>definition in this example highlights in red the new scope locations. The child scopes are necessary because the repeater evaluates{{name}}expression,but depending on which scope the expression is evaluated it produces different result. Similarly the evaluation of{{department}}prototypically inherits from root scope,as it is the only place where thedepartmentproperty is defined.

Retrieving Scopes from the DOM.

Scopes are attached to the DOM as$scopedata property,and can be retrieved for debugging purposes. (It is unlikely that one would need to retrieve scopes in this way inside the application.) The location where the root scope is attached to the DOM is defined by the location ofng-appdirective. Typicallyng-appis placed on the<html>element,but it can be placed on other elements as well,if,for example,only a portion of the view needs to be controlled by Angular.

To examine the scope in the debugger:

  1. Right click on the element of interest in your browser and select 'inspect element'. You should see the browser debugger with the element you clicked on highlighted.

  2. The debugger allows you to access the currently selected element in the console as$0variable.

  3. To retrieve the associated scope in console execute:angular.element($0).scope()or just type $scope

Scope Events Propagation

Scopes can propagate events in similar fashion to DOM events. The event can bebroadcastedto the scope children oremittedto scope parents.

Edit in Plunker

script.jsindex.html

angular.module('eventExample',[])
.controller('EventController',function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent',function() {
    $scope.count++;
  });
}]);

Scope Life Cycle

The normal flow of a browser receiving an event is that it executes a corresponding JavaScript callback. Once the callback completes the browser re-renders the DOM and returns to waiting for more events.

When the browser calls into JavaScript the code executes outside the Angular execution context,which means that Angular is unaware of model modifications. To properly process model modifications the execution has to enter the Angular execution context using the$applymethod. Only model modifications which execute inside the$applymethod will be properly accounted for by Angular. For example if a directive listens on DOM events,such asng-clickit must evaluate the expression inside the$applymethod.

After evaluating the expression,the$applymethod performs a$digest. In the $digest phase the scope examines all of the$watchexpressions and compares them with the prevIoUs value. This dirty checking is done asynchronously. This means that assignment such as$scope.username="angular"will not immediately cause a$watchto be notified,instead the$watchnotification is delayed until the$digestphase. This delay is desirable,since it coalesces multiple model updates into one$watchnotification as well as guarantees that during the$watchnotification no other$watches are running. If a$watchchanges the value of the model,it will force additional$digestcycle.

  1. Creation

    Theroot scopeis created during the application bootstrap by the$injector. During template linking,some directives create new child scopes.

  2. Watcher registration

    During template linking,directives registerwatcheson the scope. These watches will be used to propagate model values to the DOM.

  3. Model mutation

    For mutations to be properly observed,you should make them only within thescope.$apply(). Angular APIs do this implicitly,so no extra$applycall is needed when doing synchronous work in controllers,or asynchronous work with$http,$timeoutor$intervalservices.

  4. Mutation observation

    At the end of$apply,Angular performs a$digestcycle on the root scope,which then propagates throughout all child scopes. During the$digestcycle,all$watched expressions or functions are checked for model mutation and if a mutation is detected,the$watchlistener is called.

  5. Scope destruction

    When child scopes are no longer needed,it is the responsibility of the child scope creator to destroy them viascope.$destroy()API. This will stop propagation of$digestcalls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.

Scopes and Directives

During the compilation phase,thecompilermatchesdirectivesagainst the DOM template. The directives usually fall into one of two categories:

  • Observingdirectives,such as double-curly expressions{{expression}},register listeners using the$watch()method. This type of directive needs to be notified whenever the expression changes so that it can update the view.

  • Listener directives,such asng-click,register a listener with the DOM. When the DOM listener fires,the directive executes the associated expression and updates the view using the$apply()method.

When an external event (such as a user action,timer or XHR) is received,the associatedexpressionmust be applied to the scope through the$apply()method so that all listeners are updated correctly.

Directives that Create Scopes

In most cases,directivesand scopes interact but do not create new instances of scope. However,some directives,such asng-controllerandng-repeat,create new child scopes and attach the child scope to the corresponding DOM element. You can retrieve a scope for any DOM element by using anangular.element(aDomElement).scope()method call. See thedirectives guidefor more information about isolate scopes.

Controllers and Scopes

Scopes and controllers interact with each other in the following situations:

  • Controllers use scopes to expose controller methods to templates (seeng-controller).

  • Controllers define methods (behavior) that can mutate the model (properties on the scope).

  • Controllers may registerwatcheson the model. These watches execute immediately after the controller behavior executes.

See theng-controllerfor more information.

Scope$watchPerformance Considerations

Dirty checking the scope for property changes is a common operation in Angular and for this reason the dirty checking function must be efficient. Care should be taken that the dirty checking function does not do any DOM access,as DOM access is orders of magnitude slower than property access on JavaScript object.

Scope$watchDepths

Dirty checking can be done with three strategies: By reference,by collection contents,and by value. The strategies differ in the kinds of changes they detect,and in their performance characteristics.

  • Watchingby reference(scope.$watch(watchExpression,listener)) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object,changes inside it are not detected. This is the most efficient strategy.
  • Watchingcollection contents(scope.$watchCollection(watchExpression,listener)) detects changes that occur inside an array or an object: When items are added,removed,or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference,because copies of the collection contents need to be maintained. However,the strategy attempts to minimize the amount of copying required.
  • Watchingby value(scope.$watch(watchExpression,listener,true)) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy,but also the most expensive. A full traversal of the nested data structure is needed on each digest,and a full copy of it needs to be held in memory.

Integration with the browser event loop

The diagram and the example below describe how Angular interacts with the browser's event loop.

  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.

Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in the Angular execution context will benefit from Angular data-binding,exception handling,property watching,etc... You can also use $apply() to enter the Angular execution context from JavaScript. Keep in mind that in most places (controllers,services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks,or when working with third-party library callbacks.

  1. Enter the Angular execution context by callingscope.$apply(stimulusFn),wherestimulusFnis the work you wish to do in the Angular execution context.
  2. Angular executes thestimulusFn(),which typically modifies application state.
  3. Angular enters the$digestloop. The loop is made up of two smaller loops which process$evalAsyncqueue and the$watchlist. The$digestloop keeps iterating until the model stabilizes,which means that the$evalAsyncqueue is empty and the$watchlist does not detect any changes.
  4. The$evalAsyncqueue is used to schedule work which needs to occur outside of current stack frame,but before the browser's view render. This is usually done withsetTimeout(0),but thesetTimeout(0)approach suffers from slowness and may cause view flickering since the browser renders the view after each event.
  5. The$watchlist is a set of expressions which may have changed since last iteration. If a change is detected then the$watchfunction is called which typically updates the DOM with the new value.
  6. Once the Angular$digestloop finishes,the execution leaves the Angular and JavaScript context. This is followed by the browser re-rendering the DOM to reflect any changes.

Here is the explanation of how theHelloworldexample achieves the data-binding effect when the user enters text into the text field.

  1. During the compilation phase:
    1. theng-modelandinputdirectiveset up akeydownlistener on the<input>control.
    2. theinterpolationsets up a$watchto be notified ofnamechanges.
  2. During the runtime phase:
    1. Pressing an 'X' key causes the browser to emit akeydownevent on the input control.
    2. Theinputdirective captures the change to the input's value and calls$apply("name = 'X';")to update the application model inside the Angular execution context.
    3. Angular applies thename='X';to the model.
    4. The$digestloop begins
    5. The$watchlist detects a change on thenameproperty and notifies theinterpolation,which in turn updates the DOM.
    6. Angular exits the execution context,which in turn exits thekeydownevent and with it the JavaScript execution context.
    7. The browser re-renders the view with the updated text.
原文链接:https://www.f2er.com/angularjs/149446.html

猜你在找的Angularjs相关文章