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


Scope

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


Scope的特点

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

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

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

效果图:

在上面的例子当中,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;
}

效果图:

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

在DOM树上检索scope

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

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

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

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

3、可以在控制台使用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++;
});
}]);

效果图:

点击这里查看示例

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的生命周期有如下几个阶段:

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

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

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

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

5、scope的销毁 -- 当一个child scope不在需要的时候,那么你就可以调用scope.$destroy() 方法去销毁它们。这个动作将会停止$digest销毁广播的向下传递,并且也允许内存去回收child scope的所使用的内存。

scope和指令

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

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

2、监听类(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会在以下几种情况下相互影响:

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

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

3、控制器可以为model注册watche 监视,这些监视会在controller的动作加载之后立即启动。

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

Scope $watch 的性能注意事项

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

scope $watch 的深度

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

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

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

3、通过value方式:也就是scope.$watch (watchExpression, listener, true)方法来进行监视,这种方式会监视到被监视对象的所有子元素,无论是间接子元素还是直接子元素,都会被监视。他是监视最全面的,同时也是性能代价最大的。在销毁阶段,它会遍历嵌套的所有数据,并且会copy一个副本到内存中去。所以,它的性能就得你自己评估是否适当了。建议还是不要深度太大,层级太多,不然性能很差了。

图示如下:

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

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

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

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

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

图片描述:

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

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

1、通过调用scope.$apply(stimulusFn)方法进入Angular的执行上下文环境,其中stimulusFn是你希望在Angular中执行的工作.

2、Angular会执行stimulusFn()方法,通常这种工作都会修改应用程序的状态。

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

4、$evalAsync队列用于调度那些不在当前堆栈(我理解为Angular环境)中工作,并且要再浏览器进行渲染之前的工作。比如 ,通常setTimeout(0)的调用完成了,但是受到延迟的影响或者那些可能因为在事件执行后,浏览器重新渲染View的时候造成的画面(UI)闪烁的问题的影响的工作,就会被$evalAsync队列调度。

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

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

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

一、在编译阶段:

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

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

二、在运行阶段:

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

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

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

4、开始$digest轮询;

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

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

7、浏览器重新渲染view视图,刷新UI。

AngularJS–Scope(作用域)的更多相关文章

  1. AngularJS Scope(作用域)

    1. AngularJS Scope(作用域) Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带. Scope 是一个对象,有可用的方法和属性. Sc ...

  2. AngularJS学习之旅—AngularJS Scope作用域(五)

    1.AngularJS Scope(作用域) Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带. Scope 是一个对象,有可用的方法和属性. Sco ...

  3. AngularJs之Scope作用域

    前言: 上篇博文AngularJs之directive中说了Scope作用域是个大坑,所以拿出来作为重点总结! 什么是scope AngularJS 中,作用域是一个指向应用模型的对象,它是表达式的执 ...

  4. 理解AngularJS的作用域Scope

    AngularJS中,子作用域一般都会通过JavaScript原型继承机制继承其父作用域的属性和方法.但有一个例外:在directive中使用scope: { ... },这种方式创建的作用域是一个独 ...

  5. 理解angularJS中作用域$scope

    angularJS中作用域是什么 作用域(scope)是构成angularJS应用的核心基础,在整个框架中都被广泛使用,因此了解它如何工作是非常重要的 应用的作用域是和应用的数据模型相关联的,同时作用 ...

  6. AngularJs(五)从Controller控制器谈谈$scope作用域

    大纲 用于简单示例和简单应用的controller 应用 多个controller应用的作用域问题 controller继承作用域问题 Controller的创建 AngularJs controll ...

  7. 关于AngularJS学习整理---浅谈$scope(作用域) 新手必备!

    作为初次接触 AngularJS的新手,想要深层理解里面的内容短时间还是不可能的,所以标题写了浅谈字样,以下内容是参考各位大神以及相关书籍整理加个人理解,出现错误的地方请大家指正. $scope(作用 ...

  8. Angular JS 学习之 Scope作用域

    1.Scope作用域是应用在HTML(视图)和JavaScript(控制器)之间的纽带: Scope是一个对象,有可用的方法和属性: Scope可应用在视图和控制器上: 2.当你在AngularJS中 ...

  9. AngularJS $scope 继承性 作用 生命周期

    一.基本概念 作用域是一个指向应用模型的对象,相当于MVVM中的ViewModel,能绑定数据(属性)和行为(方法),能监控表达式和传递事件,是实现双向绑定的基础,是应用在 HTML (视图) 和 J ...

随机推荐

  1. JavaScript操作DOM节点

    DOM (文档对象模型(Document Object Model)) 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口.在网 ...

  2. 又一流氓推广Microsoft Edge,我勒个去

    最新的Windows10 的升级也是醉了,不得不吐槽一个非常流氓的浏览器推广:Microsoft Edge(这小婊砸). 为了将之前的历史包袱IE干掉,这次微软也是蛮拼的,直接把IE从电脑里干掉了,你 ...

  3. Java基础---网络编程

    第一讲     概述 1.网络模型:OSI参考模型和TCP/IP参考模型 图示: 一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP. 通常用 ...

  4. [2012-06-29]sed根据行号范围执行替换

    测试数据: personball@vostro:SHELL$cat aaa <instrumentation android:name="aaa" android:name= ...

  5. 关于package.json的理解

    在我们打包项目的时候或者使用node的时候,常常会看到package.json这个文件,里面乱七八糟的一大堆json,开始的时候没注意,以为是使用node或者npm的时候自动创建的,后来自己写demo ...

  6. JVM(二)JVM内存布局

    这几天我再次阅读了<深入理解Java虚拟机>之第二章"Java内存区域与内存溢出异常",同时也参考了一些网上的资料,现在把自己的一些认识和体会记录一下.  (本文为博主 ...

  7. 最常见的三个排序(冒泡、直接插入、快速)的JS实现

    //冒泡排序function bubble(arr){ for(var i=0;i<arr.length;i++){ for(var j=0;j<arr.length-i;j++){ if ...

  8. PCB Design_经验之谈

    所谓覆铜,就是将PCB上闲置的空间作为基准面,然后用固体铜填充,这些铜区又称为灌铜.敷铜的意义在于,减小地线阻抗,提高抗干扰能力:降低压降,提高电源效率:与地线相连,还可以减小环路面积.也出于让PCB ...

  9. 团队作业8——第二次项目冲刺(Beta阶段)5.27

    1.当天站立式会议照片 会议内容: 本次会议为第七次会议 本次会议在陆大楼2楼召开,本次会议内容: ①:检查总结上次任务完成情况 ②:安排今天的分工 ③:对昨天的问题进行讨论 2. 每个人的工作 (有 ...

  10. KKlist团队目录

    KKlist团队目录 一.Daily Scrum Meeting[Alpha] 4.22 day one 4.23 day two 4.24 day three 4.25 day four 4.26 ...