原文:http://docs.angularjs.org/guide/scope
scope用于指代程序的model,它是angularjs表达式的运行上下文。Scope使用了分层结构,与程序相应的DOM结构相似。Scope可以观察表达式,也可以传播事件。
Scopes提供了API$watch
可观察model的变化
Scopes提供了API$apply
可把AngularJs体系(controllers,services,angularjs event handlers)的model的变化传播到系统内至view层。
Scopes可访问共享的model属性,同时也可以嵌入到孤立(isolate)的程序组件之中。Scope以原型方式从上级Scope继承属性
Scope提供了表达式的运行上下文。例如,{{username}}
只有在一个提供了username
属性的scope中才能正确地求值,否则它只是一个普通的字符串
Scope是程序的controller和view之间的胶水。在模板linking阶段,directives会在scope上设置$watch
表达式。$watch
允许当属性变化时,directives会得到通知,因此可更新相应的DOM上的内容。
controller与directives都有到scope的引用,但它们两者之间没有引用。这么做让controller与directive与DOM之间都保持独立。这一点很重要,因为它让controllers没有绑死在代码中,大大方便了写测试用例。
index.html
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="MyController">
Your name:
<input type="text" ng-model="username">
<button ng-click='sayHello()'>greet</button>
<hr>
{{greeting}}
</div>
</body>
</html>
**script.js**
function MyController($scope) {
$scope.username = 'World';
$scope.sayHello = function() {
$scope.greeting = 'Hello ' + $scope.username + '!';
};
}
jsfiddle
在上面的例子中,请注意在`MyController`中,把`World`赋给了`username`。然后,scope将这次赋值通知了`input`,input中会显示新值。这里演示了controller如何把数据写入到scope中。
与之相似,controller也可以把行为赋给scope,比如那个`sayHello`方法,当用户点击了`greet`按钮时将会调用它。该方法会从scope中读取`username`属性,并新建了一个`greeting`属性。这里演示了HTML上的部件可以自动更新scope上的属性。
逻辑上,`{{greeting}}`的渲染涉及到:
在模板中{{greeting}}
定义处的DOM节点上取得scope。本例中,该scope就是传给MyController
的 scope。(稍后讨论)
在scope中对greeting
表达式进行求值,并把结果赋给所在的DOM元素的文本。
你可以把scope和它的属性看作是用来演示view的数据。Scope是与view相关的所有数据的唯一合法可信来源。
为了提高可测试性,将controller与view分开是可取的。因为它允许我们在测试行为的同时,无须考虑渲染的细节。
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!');
});
每个angularjs程序,都仅有一个root scope,但可以有多个子scope。
程序可以拥有多个子scopes,是因为一些directives可以创建新的子scopes(参见directive章节)。当新的scopes创建之后,它们就会被当作子scope。这样就可以创建出一个与它们所在的DOM并行的树结构。
当Angular对{{username}}
进行求值,它首先会去到所在元素关联的scope中查看username
属性。如果找不到,则到上级scope继续,直到到达了root scope。在Javascript中,这种行为被称为原型继承,angularjs中的scope也是原型继承。
下面的例子演示了程序中的scopes,以及它们属性的原型继承。
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<div ng-controller="EmployeeController">
Manager: {{employee.name}} [ {{department}} ]<br>
Reports:
<ul>
<li ng-repeat="employee in employee.reports">
{{employee.name}} [ {{department}} ]
</li>
</ul>
<hr>
{{greeting}}
</div>
style.css
/ remove .doc-example-live in jsfiddle /
.doc-example-live .ng-scope {
border: 1px dashed red;
}
script.js
function EmployeeController($scope) {
$scope.department = 'Engineering';
$scope.employee = {
name: 'Joe the Manager',
reports: [
{name: 'John Smith'},
{name: 'Mary Run'}
]
};
}
jsfiddle
注意angularjs会自动在scope所关联到的元素上增加一个ng-scope
的class。在css中,定义其为边框红色高亮。
子scope是必须的,因为repeater对表达式{{employee.name}}
求值,不同的scope将产生不同的结果。而{{department}}
属性以原型方式继承于root scope,因为只在root scope中定义了department
属性。
Scope会以$scope
为名添加到DOM的属性中,也可以被取回(常用于调试,通常我们不会在程序中以这种方式来取$scope)。root scope所在的位置是声明ng-app
这个directive的元素,通常是<html>
,但也可以放在其它元素中,比如只你想让页面中的某一块归angularjs管。
如何在debugger中检查scope:
在浏览器中右键点击感兴趣的元素,并选择“inspect element”。你可以看到浏览器调试器会将你选中的元素进行高亮。
debugger允许你在console中,以$0
变量来访问当前选中的元素
在console中取出关联的scope的命令:angular.element($0).scope()
Scope可以用与DOM事件相似的风格来传播事件。事件可以被broadcasted
到下级scope中,或者emitted
到上级scope中。
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<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
function EventController($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() {
$scope.count++;
});
}
jsfiddle
浏览器接受到一个事件的通常处理流程,是执行其相应的JavaScript回调函数。一旦回调函数执行完成后,浏览器将重新渲染DOM,然后返回,等待下一个事件。
当浏览器在anguljar的运行上下文之外执行JavaScript代码时,angularjs将收不到model的修改。为了能够正确地处理model的修改,必须使用angularjs提供的$apply
方法进入到angularjs的运行上下文中。强调一遍,只有在angularjs的$apply
方法中进行的model修改,才能正确的通知给angularjs。
举例来说,如果一个directive来监听DOM事件,比如ng-click
,它必须在$apply
方法中来对表达式求值。
当对表达式求完值后,$apply
会执行一个$digest
的方法。在$digest
阶段,scope会检查所有$watch
表达式,并与之前的值比较。这个“脏数据”检查,是异步执行的。这表明,对于$scope.username=""angular"
这样的赋值不会立刻通知$watch
,相反,$watch
通知会延迟到$digest
阶段。这种延迟的做法是可取的,因为它可以把对同一个$watch
的多次通知合并为一个,并且保证了对一个$watch
进行通知时其它的$watch
不会处于运行状态。如果一个$watch
改变了model的值,将会强制增加一个新的$digest
周期。
root scope在程序引导期由$injector
创建。在模板的linking阶段,某些directives将会产生新的子scopes
注册观察器
在模板linking阶段,directives在scope上注册观察器。这些观察器用来把model的值传播给DOM
Model变化
为了能正确地观察到model的变化,我们应该让它们处于scope.$apply()
之中。(Angularjs的api隐式地完成了这件工作,因为我们在controller中进行同步工作,或在$http
或$timeout
中进行异步工作时,不需要再手动调用$apply
)
变化的观察
当$apply
结束时,angular在root scope上执行一个$digest
周期,之后将传播给所有的子scopes。在$digest
周期,如果model发生了变化,所有的$watch
表达式或函数都将被检查,并且会调用$watch
的监听器
Scope的销毁
当子scope不再需要时,子scope的创建者有责任通过$scope.$destroy()
API来销毁它们。这次使$digest
停止向该子scope继承传播,也同时允许垃圾回收器来回收子scope占用的内存。
在编译阶段,编译器从DOM模板中来匹配directives。这些directives通常会被归到两类中:
观察类(Observing)directives,比如表达式{{expression}}
,使用$watch
来注册监听器。每当表达式变化了,都需要向这类directive通知,好让它们去更新view
监听类(Listener)directives,比如ng-click
,在DOM上注册一个监听器。当DOM的监听器触发了,这类directive将执行相关的表达式,并使用$apply
方法来更新view
当外部事件(例如用户操作,timer或者XHR)到达了,相关联的表达式必须使用$apply
方法把它们应用到scope中,才能让所有的监听器(listener)正确的更新。
在大多数情况下,directives和scopes互相作用,但不创建新的scope实例。但是,某些directives,比如ng-controller
和ng-repeat
,会创建新的scope并把它们关联到相应的DOM上。
你可以从任意的DOM元素上使用angular.element(aDomElement).scope()
方法,来取得相应的scope。
在以下情况,scopes和controllers互相作用:
Controller使用scopes把controller中的方法暴露给模板(参考ng-controller)
Controller定义一些可以改变model(scope上的属性)的方法
Controller可以在model上注册观察器(watches)。当controller执行了操作,相应的观察器也会立刻执行。
详情参考ng-controller
$watch
的性能考虑在angularjs中,当属性变化时,对scope进行“脏数据”检查是一件很常见的操作,因此该检查必须很高效。注意在检查中,不要进行任何的DOM访问,因为DOM访问要比JavaScript对象访问慢上几个数量级。