AngularJS–Scope(做用域)

点击查看AngularJS系列目录
转载请注明出处:http://www.cnblogs.com/leosx/css


 

Scope

Scope 是一个应用程序的模块的对象。它是表达式的执行上下文。它充斥在DOM树的各个层级上。做用域Scope能够监控表达式也能够广播事件(监控表达式,就是WPF中的属性变动通知,至关有做用哟!)。html

 


 

Scope的特色angularjs

Scope有一个监控方法($watch),用它来监视model(模型)的变化,也就是上面所说的监视并作变动通知。chrome

Scope有一个($apply)方法,咱们用它就能够去执行一些来自非Angular的代码,或者第三方内库功能。它的好处就是让第三方函数加入了Angular框架,走了AngularJS的生命周期,咱们就能够在AngularJS的生命中期中详细的控制了。api

Scope也能够嵌套到应用程序限制访问的一些组件中去。而且能够提供一些共享的属性。嵌套进去的Scope是一个“子Scope”或者是一个“独立Scope”。须要注意的是:“子Scope”是继承自它的父级Scope的,它有父级Scope的属性和方法。而“独立Scope”则没有继承父级Scope。要查看更多独立Scope(isolated scopes),请点击连接进行查看。Scope为咱们的表达式提供了上下文。例如单纯的{{username}}表达式是没有任何意义的,由于它没有上下文,访问不到username变量。为了使得表达式起做用,咱们就要在表达式对应的组件中的Scope上定义一个username属性,而且为它赋值,而后这个表达式就有上下文了,就能够访问到uername属性而且渲染显示。数组

 

Scope之VM(ViewModel)浏览器

Scope是应用程序(app)的controller和view之间的粘合剂。在模板(template)进行连接(linking)期间,会使用scope的$watch 表达式去监视一些指令所引用的对象,换句话说就是$watch 能够对Scope上的model(ViewModel)进行监控,当model上的属性变化时,就会通知UI进行更新,若是咱们自定义了监视。那么一样会调用咱们自定义的监视代码。这个就是WPF上的属性变动通知。是至关有用的东东。来一个例子:网络

第一个文件:index.htmlapp

<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 + '!';
  };
}]);

 

效果图:

image

在上面的例子当中,MyController 控制器的 username 属性的值是World 。它被ng-model指令分配到了input 对象上,进行了一个双向绑定,也就是说,当用户在UI界面中,在input中输入数据时,会自动把数据更新到username 属性上,若是在js中,修改username 属性的值,那么一样的,Angular会通知UI进行更新input的显示值。这就是双向绑定。

 

Scope的继承

每个AngularJS应用程序有且只有一个根scope(root scope),可是,容许拥有不少个子集scope。

在一个Angular应用程序中,是能够拥有多个scope的,由于有一些指令,是会自动建立scope的(参照指导文件,以查看哪些指令建立新的scope)。当指令自动建立scope的时候,会继承父级scope。也就是拥有上级scope的全部属性和方法。

例如,咱们在执行 {{name}}表达式的时候,首先会在scope中寻找这个name属性,若是找不到,那么会自动去父级scope上找name属性,以此类推,直到rootScope为止。

下面这个例子演示了scope的应用,也是一个原型继承(prototypical inheritance)。例子中,明确标识出了scope的边界。

第一个文件:index.html

<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>

 

第二个文件:script.js

angular.module('scopeExample', [])
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
  $scope.name = 'World';
  $rootScope.department = 'Angular';
}])
.controller('ListController', ['$scope', function($scope) {
  $scope.names = ['Igor', 'Misko', 'Vojta'];
}]);

 

第三个文件:style.css

.show-scope-demo.ng-scope,
.show-scope-demo .ng-scope  {
  border: 1px solid red;
  margin: 3px;
}

 

效果图:

image

image

请注意:当scope被附加到了元素上以后,会自动的为这个元素加上ng-scope 样式。这个例子中的<style> 样式用来标识scope边界。

在DOM树上检索scope

scope被附加到DOM上的$scope属性上(在应用程序内,是不能够这样去检索scope的哦!)。其中rootscope会被附加到ng-app 指令所对应的DOM上。一般,ng-app 指令会被附加到<html> 元素上去。也能够被附加到其余的标签上去。

让咱们来使用chrome的debugger来介绍下scope

一、在chrome浏览器中,右击你要查看的元素,在右键菜单中选择【审查元素】,而后你就能够看见这个元素被debugger高亮显示出来了。

二、调试器容许咱们在控制台中使用$0 变量去访问当前选中的元素。

三、能够在控制台使用angular.element($0).scope() 或者$scope去访问选中元素所在的scope

 

Scope的事件广播

scope能够以相似DOM事件的样子进行广播一个事件。该事件能够被广播到子集scope或者父级scope上去。咱们来看一个例子:

文件一:index.html

<div ng-controller="EventController">
  Root scope <tt>MyEvent</tt> count: {{count}}
  <ul>
    <li ng-repeat="i in [1]" ng-controller="EventController">
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
      <br>
      Middle scope <tt>MyEvent</tt> count: {{count}}
      <ul>
        <li ng-repeat="item in [1, 2]" ng-controller="EventController">
          Leaf scope <tt>MyEvent</tt> count: {{count}}
        </li>
      </ul>
    </li>
  </ul>
</div>

 

文件二:script.js

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

 

效果图:

image

 

点击这里查看示例

 

Scope的生命周期

正常状况下,浏览器接收到事件触发信息后,会直接执行回调函数。一旦回调完成,浏览器从新渲染DOM并显示出来,也就是更新UI,而且马上返回,以便等待接收其它的事件触发。

当浏览器在AngularJS的scope(上下文)以外调用JavaScript的时候,AngularJS是没法知道model被修改了,也就没法实现双向绑定了。要想正确的实现model的双向绑定,那么就要把JavaScript使用$apply 方法加入到AngularJS的生命周期当中去,这样AngularJS以外的JS也能够正确的进行双向绑定了。例如,若是一个指令,监听DOM事件,好比:ng-click它也是在$apply 方法中进行调用的,这样才能正确的响应数据模型(VM-ViewModel)。实现双向绑定。

在表达式计算完毕后,$apply 方法会调用$digest。在执行$digest方法的时候,scope会检查全部的$watch表达式;并将它们与之前的值进行比较。注意:这个值的变动检查是异步方式进行的。这意味着,若是执行了诸如:$scope.username="angular"的操做将不会当即更新UI;由于$watch 的通知尚未发出来。$watch 的变动通知会被延迟到$digest 执行的时候进行。这个延迟是合理的哈!由于它组合了model的多个属性变动通知到一个$watch 变动通知表单中。这样就能够保证在$watch 的通知表单在执行通知的时候,没有其它的通知也在同时进行。

Scope的生命周期有以下几个阶段:

一、建立阶段  --  让AngularJS应用程序在启动$injector的期间,就会建立 root scope (惟一的根Scope)。当模板(template)在进行连接的阶段,一些指令会建立子scope(child scope)。

二、监视器的注册 -- 在template(模板)连接期间,指令会经过scope的$watch注册监视。这些监视用于将变动广播到DOM上,以进行UI变动。

三、model变化 -- 要想model的变化被正确的监视到,你必须把更改model的表达式放在scope.$apply()方法中执行。Angular在这方面作的很精简的,在controller中执行model修改或者在诸如$http, $timeout 或者 $interval等异步服务中修改model的话,都会自动去调用$apply 方法的,就再也不用本身去调用$apply 方法了。

四、观察变化 -- 在$apply方法结束的时候,Angular会调用rootscope上的$digest方法进入销毁阶段,而后再广播给child scope,告诉他们执行$digest方法,进入他们的销毁阶段。在销毁阶段,全部使用$watch进行监视的model的表达式或者属性还有方法都会被检查是否有变动,若是有变动,那么久会执行通知。

五、scope的销毁 -- 当一个child scope不在须要的时候,那么你就能够调用scope.$destroy() 方法去销毁它们。这个动做将会中止$digest销毁广播的向下传递,而且也容许内存去回收child scope的所使用的内存。

 

scope和指令

在编译阶段,编译器会去匹配对DOM模板上的指令。这些指令一般分为如下两类:

一、监视类(Observing)指令,例如:双花括号{{表达式}},它使用$watch() 方法去进行监视。这类表达式在表达式变化了的时候,会通知UI进行界面更新。

二、监听类(Listener)指令,例如ng-click指令,注册一个对DOM的监听,当DOM事件触发时,会去执行它本身的表达式,而且使用$apply() 方法去更新UI界面。

当接收到一个外部事件(例如用户动做,定时器或XHR),它们的表达式必须在$apply()方法中去执行,以便全部监视者能正确更新数据到所本身所在的scope上。

 

指令建立Scope

在大多数状况下,directives (指令)和scope会相互影响,可是不会建立出新的scope出来。然而,有一些指令,诸如:ng-controllerng-repeat指令, 它们会建立一个child scope而后附加到对应的DOM元素上去。你能够调用angular.element(aDomElement).scope() 方法去取到任何DOM元素身上的scope信息。

 

控制器(controller)和scope

控制器和scope会在如下几种状况下相互影响:

一、控制器(controller)使用scope来暴露方法和属性给模板(template)使用和访问。

二、controller定义的一些方法(或者行为behavior),能够去改变scope上的属性值。

三、控制器能够为model注册watche 监视,这些监视会在controller的动做加载以后当即启动。

查看更多和ng-controller相关的信息,点击这里

 

Scope $watch 的性能注意事项

在Angular中,scope对model属性的变动检查是一个公共的方法。正因如此,变动检查功能必须是有效的。应该注意的是,变动检查并无去作任何的DOM操做的哦!访问DOM元素会要比访问JavaScript的属性的速度要慢。

 

scope $watch 的深度

变动检查能够用三种策略来实现:经过引用(reference)、经过集合(collection contents)、经过value。这几种方式的性能是有不一样的;并且方式也不同。

一、经过引用方式(by reference):也就是scope.$watch(watchExpression, listener)方法。当检测到变化时,$watch表达式监控的全部值会更新,而且总体返回。需要注意的是,若是咱们监视的是一个Array数组或者一个对象时,对象或数组里面的数据变化时,是不会被检测到的。这个策略的性能是最好的。

二、经过集合(collection contents)方式:也就是scope.$watchCollection(watchExpression, listener)方法;这个方法就弥补了上面一种方式的不足,这种方式会监视到数组或者对象的内部变化。当为一个数组增长,删除或者从新排序时,都会进行变动通知的。不过这种方式并非嵌套监视的,它只监视被监视集合或者对象下的直接子元素的变化,不会监视子元素的子元素。相比引用方式去监听,这种方式性能上确定会差一些。可是,某些状况下,使用它是最好的选择。

三、经过value方式:也就是scope.$watch (watchExpression, listener, true)方法来进行监视,这种方式会监视到被监视对象的全部子元素,不管是间接子元素仍是直接子元素,都会被监视。他是监视最全面的,同时也是性能代价最大的。在销毁阶段,它会遍历嵌套的全部数据,而且会copy一个副本到内存中去。因此,它的性能就得你本身评估是否适当了。建议仍是不要深度太大,层级太多,否则性能不好了。

图示以下:

concepts-scope-watch-strategies (1)

 

和浏览器的事件循环的集成

下面的图表和例子描述了浏览器的事件循环如何和Angular相互做用的。

一、浏览器的事件循环等待事件的到来。一个事件通常是用户的交互触发,定时器事件,或网络事件。

二、事件的回调将会在事件触发时,进入该事件的JavaScript环境进行执行。回调函数能够修改DOM的结构。

三、一旦回调执行,浏览器会离开JavaScript环境,而且会从新渲染修改后DOM到UI界面。

图片描述:

concepts-runtime

 

Angular经过提供一个属于本身的事件轮询处理机制去修改了正常的JavaScript流的执行。因此JavaScript的执行环境就分为了正常的JavaScript流环境和Angular事件环境两种状况。只有那些在Angular执行上下文环境中执行的操做,才会具备Angular提供的诸如:数据绑定,异常处理(exception handling),属性监视等功能。你也可使用$apply()方法把常规的JavaScript代码加入到Angularjs的执行上下文环境中进行执行,这样咱们的JavaScript代码就能够具备上面提到的那些Angular提供的功能了。请记住,在大多数地方诸如:controllers, services等指令中,当事件处理完成后,都会自动为你调用$apply()方法。也就是你不用本身手动调用$apply()方法了。 只有咱们自定义的JavaScript代码,或者本身直接操做DOM的JavaScript代码,或者第三方类库的回调函数才会须要手动调用$apply()方法来加入Angular的执行上下文环境中。

$apply()方法的使用以及执行流程大体有以下几步:

一、经过调用scope.$apply(stimulusFn)方法进入Angular的执行上下文环境,其中stimulusFn是你但愿在Angular中执行的工做.

二、Angular会执行stimulusFn()方法,一般这种工做都会修改应用程序的状态。

三、Angular进入轮询($digest loop)。这个轮询中会有两个小的轮询,它们分别是进行$evalAsync队列处理,和执行和$watch 监视相关的工做。$digest 轮询会一直迭代,直到模型(model)保持稳定,也就意味着$evalAsync队列是空的了,而且$watch 监视表单中再也不有任何改变。

四、$evalAsync队列用于调度那些不在当前堆栈(我理解为Angular环境)中工做,而且要再浏览器进行渲染以前的工做。好比 ,一般setTimeout(0)的调用完成了,可是受到延迟的影响或者那些可能由于在事件执行后,浏览器从新渲染View的时候形成的画面(UI)闪烁的问题的影响的工做,就会被$evalAsync队列调度。

五、$watch 的集合是一组在最后一次迭代的时候可能发生了变化的表达式。若是检测到了变化,$watch方法就会被调用,这个方法一般是把DOM上对应元素的旧值更新为如今变化后的新值(这时改变了DOM,浏览器并无从新渲染)。

六、一旦$digest 轮询完成了,便会离开Angular的执行上下文。在这以后,浏览器就开始了从新渲染DOM,也就是刷新UI界面了。

 

 

下面阐释了当用户在文本框中输入一段Hello world文字后,是如何实现数据的绑定效果的。

1、在编译阶段:

一、ng-model 指令和input directive 指令会监听<input> 控件的keydown 事件。

二、插值(interpolation)使用$watch 去注册name 的变动时的通知。

2、在运行阶段:

一、在键盘上按下'X' 键,使得浏览器去激活这个<input> 控件的keydown 事件;

二、input (点击我,查看有哪些input指令)指令捕获到了输入值的改变,而且调用了$apply("name = 'X';") 方法去更新Angular执行上下文的模型(model);

三、Angular把model上的name = 'X';

四、开始$digest轮询;

五、$watch 的集合监视到了name 属性的改变,而且通知了interpolation,从而更新了DOM;

六、Angular退出执行上下文,这样就会退出keydown 事件所在的JavaScript的执行上下文;

七、浏览器从新渲染view视图,刷新UI。

相关文章
相关标签/搜索