文章目录
什么是作用域
作用域是一个存储应用数据模型的对象,对应于MVC中的M(model)层
作用域的层级结构对应于DOM Tree 结构
为表达式提供了一个执行上下文(context),作用域可以监听表达式的变化并传播事件
如何创建作用域
angular可以通过指令
创建子作用域且继承自父作用域
1、 ng-repeat。
2、 ng-include。
3、 ng-switch。
4、 ng-controller。
5、 directive (scope: true)。
6、 directive(transclude: true)。
<div ng-repeat="(key, value) in cache.info()">
<span ng-bind="key"></span>
<span>: </span>
<b ng-bind="value"></b>
</div>
上面这串代码就创建了一个作用域,就可以使用$scope.key
和$scope.value
来在作用域中定义变量了
作用
- 提供
$watch()
方法监听数据模型的变化 - 提供
$spply()
方法把不是由Angular触发的数据模型的改变引入Angular的控制范围内(如控制器,服务,及Angular事件处理器等) - 作用域提供了基于原型链继承其父作用域属性的机制,就算是嵌套于独立的应用组件中的作用域也可以访问共享的数据模型(这个涉及到指令间嵌套时作用域的几种模式)
- 作用域提供了 表达式 的执行环境,比如像
{{username}}
这个表达式,必须得是在一个拥有属性这个属性的作用域中执行才会有意义,也就是说,作用域中可能会像这样scope.username
或是$scope.username
,至于有没有 $ 符号,看你是在哪里访问作用域了
作用域作为数据模型使用
作用域是Web应用的控制器和视图之间的粘结剂。在Angular中,最直观的表现是:在自定义指令中,处在模版的 链接(linking) 阶段时, 指令(directive)会设置一个 $watch 函数监听着作用域中各表达式(注:这个过程是隐式的)。这个 $watch 允许指令在作用域中的属性变化时收到通知, 进而让指令能够根据这个改变来对DOM进行重新渲染,以便更新已改变的属性值(注:属性值就是scope对象中的属性,也就是数据模型)。
其实,不止上面所说的指令拥有指向作用域的引用,控制器中也有(注:可以理解为控制器与指令均能引用到与它们相对应的DOM结构所处的作用域)。 但是控制器与指令是相互分离的,而且它们与视图之间也是分离的,这样的分离,或者说耦合度低,可以大大提高对应用进行测试的工作效率。
注:其实可以很简单地理解为有以下两个链条关系:
- 控制器 --> 作用域 --> 视图(DOM)
- 指令 --> 作用域 --> 视图(DOM)
作用域分层结构
如上所说,作用域的结构对应于DOM结构,那么最顶层,和DOM树有根节点一样,每个Angular应用有且仅有一个$rootScope
,当然啦,子级作用域就和DOM树的子节点一样,可以有多个的。
应用可以拥有多个作用域,比如 指令 会创建子级作用域(至于指令创建的作用域是有多种类型的,详情参加指令相关文档)。一般情况下,当新的作用域被创建时,它是以嵌入在父级作用域的子级的形式被创建的,这样就形成了与其所关联的DOM树相对应的一个作用域的树结构。(译注:作用域的层级继承是基于原型链的继承,所以在下面的例子中会看到,读属性时会一直往上溯源,直到有未知)
作用域的分层的一个简单例子是,假设现在HTML视图中有一个表达式 {{name}} ,正如上面解释过,Angular需要经历取值和计算两个阶段才能最终在视图渲染结果。那么这个取值的阶段,其实就是根据作用域的这个层级结构(或树状结构)来进行的。
- 首先,Angular在该表达式当前所在的DOM节点所对应的作用域中去找有没有 name 这个属性
- 如果有,Angular返回取值,计算渲染;如果在当前作用域中没有找到,那么Angular继续往上一层的父级作用域中去找 name 属性,直到找到为止,最后实在没有,那就到达
$rootScope
了,如果$rootScope
里还是没有找到就返回空
接下来这个demo用一个图具体展示了作用域的层级结构,让你可以有更直观的了解。
<div ng-controller="nameCtrl">
<p ng-repeat="name in names">
{{greeting}},{{ name }}! <br>
</p>
</div>
var app = angular.module('myApp',[])
app.controller('nameCtrl',[
'$scope','$rootScope',function ($scope,$rootScope) {
$rootScope.greeting = 'Hello'
$scope.names = ['Igor', 'Misko', 'Vojta']
}
])
在DOM中抓取作用域
作用域对象是与指令或控制器等Angular元素所在的DOM节点相关联的,也就是说,其实DOM节点上是可以抓取到作用域这个对象的(当然,为了调试偶尔会用,一般不用)。 而对于 $rootScope 在哪里抓呢?它藏在 ng-app 指令所在的那个DOM节点之中,请看更多关于 ng-app 指令。通常,ng-app 放在 标签中, 当然,如果你的应用中只是视图的某一部分想要用Angular控制,那你可以把它放在想要控制的元素的最外层。
那来看看如何在调试的时候抓取作用域吧:
- 右键选去你想审查的元素,调出debugger,通常F12即可,这样你选中的元素会高亮显示
- 此时,调试器(debugger)允许你用变量
$0
来获取当前选取的元素 - 在console中执行
angular.element($0).scope()
即可看到你想要查询的当前DOM元素节点绑定的作用域了,这里分析一下scope
对象
1.$id: 创建一个作用域id就增1(初始id为1)。
2.$$phase: 这个是脏检查的一个阶段变量
3.$parent: 父级作用域。
4.$$nextSibling: 上一个兄弟作用域。
5.$$prevSibling: 下一个兄弟作用域。
6.$$childHead: 第一个子作用域。
7.$$childTail: 最后个子作用域。
8.$$watchers: 此作用域下的监听对象(是一个数组来的)。
9.$$destroyed : 初始值为false,当摧毁的时候为true。
10.$$listeners: 此作用域下的注册的监听器事件。
11.$$listenerCount:此作用域下(包括子作用域)的注册监听器事件总数。
12.$$watchersCount:此作用域下(包括子作用域)注册的watchers对象对应的总数。
13.$root
: 所有的作用域通过这个属性可以关联到$rootScope。
作用域的事件传播
作用域可以像DOM节点一样,进行事件的传播。主要是有两个方法:
$broadcast('name', 'args')
:向下传播$emit('name', 'args')
:向上传播$scope.$on('name', function(event,data){})
监听事件和接受数据
在$on()
中的event事件参数,其对象的属性和方法如下:
<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向上发射</button>
<button ng-click="$broadcast('MyEvent')">$broadcast向下传播</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>
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() {
$scope.count++;
});
}]);
scope的生命周期
创建→注册监听→数据模型变化→ 监测模型变化 →销毁
创建(creation)
root scope 是在应用程序启动时由 $injector
创建的。另外,在指令的模版链接阶段(template linking),指令会创建一些新的子级 scope。
注册监听(Watcher registration)
作用域一旦生成,指令就会在它身上注册一个监视,就是我们平时用到的$scope.$watch()
,这些 $watch
用来监测数据模型的更新并将更新值传给DOM。
数据模型变化(Model mutation)
要想让数据模型的变化能够很好的被Angular监测,需要让它们在 scope.$apply()
里发生。 当然,对于Angular本身的API来讲,无论是在控制器中做同步操作,还是通过 $http 或者 $timeout 做的非同步操作, 抑或是在Angular的服务中,是没有必要手动去将数据模型变化的操作放到 $apply() 中去的,因为Angular已经隐式的为我们做了这一点。
监测模型变化 (Mutation observation)
有了变化,我们就要观察这个变化影响的范围到底有多大,那么在进入到angular环境后就会执行那个颇受诟病的脏值检查。既然作用域是原型继承下来的,而且和DOM结构平行,那显然最先应该检查就是rooScope,然后传播到所有的子作用域上,这个时候$watch,设置的一些函数,表达式等就会被执行,相应的改变发生的话就应用你设置好的函数等。
销毁(Scope destruction)
摧毁——当我们不在需要一个作用域,需要将它移除掉,原则就是谁创建谁销毁,使用的方法就是$scope.$destroy()
,这里如同apply一样,这个方法一要被调用,至于谁调用,参照原则。如果不做呢?good question,不做也不会被枪毙,只是在进行digest循环时,它仍然会被加入其中,增加性能的开销。执行完这个方法后,它占用的内存才能被释放,进而被当成垃圾回收掉。
参考
https://blog.youkuaiyun.com/lhj20084720208/article/details/78997015
https://blog.youkuaiyun.com/ws9029/article/details/94867245
https://blog.youkuaiyun.com/ws9029/article/details/94164020
https://www.cnblogs.com/liangliangjiang/p/6672228.html
https://www.jianshu.com/p/44f70e91ddf4