AngularJS做用域是一个指向应用模型的对象。它是表达式的执行环境。做用域有层次结构,这个层次和相应的DOM几乎是同样的。做用域能监控表达式和传递事件。html
{{username}}
自己是无心义的,除非把它放到指定username属性的做用域中。做用域是控制器和视图之间的“胶水”。在模板连接阶段,指令设置好做用域的$watch表达式。$watch使得指令能知晓属性的改变,这使得指令能从新渲染和更新DOM中的值。angularjs
控制器和指令都持有做用域的引用,可是不持有对方的引用。这使得控制器能从指令和DOM中脱离出来。这很重要,由于这使得控制器彻底不须要知道view的存在,这大大改善了应用的测试。express
举个例子:浏览器
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.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:app
function MyController($scope) { $scope.username = 'World'; $scope.sayHello = function() { $scope.greeting = 'Hello ' + $scope.username + '!'; }; }
上例中MyController将值World赋给了做用域中的username。而后做用域将这个赋值的操做通知给input,而后input就会被渲染成预填充了值的样子。这展现控制器如何将数据写入到做用域。函数
一样的,控制器能将行为添加到做用域,正如你看到的sayHello方法,这个方法是在用户点击'greet'按钮时被调用的。测试
逻辑上来讲,表达式{{greeting}}
的渲染须要:spa
{{greeting}}
DOM节点相关的做用域。在这个例子里,就是传入到MyController的做用域。你能够把做用域和它的属性当作是用来渲染视图的数据。做用域是视图惟一相关联的变化来源。调试
每个AngularJS应用都有一个绝对的根做用域。可是可能有多个子做用域。code
一个应用能够有多个做用域,由于有一些指令会生成新的子做用域(参考指令的文档看看哪些指令会建立新做用域)。当新做用域被建立的时候,他们会被当成子做用域添加到父做用域下,这使得做用域会变成一个和相应DOM结构一个的树状结构。
当AngularJS执行表达式{{username}}
,它会首先查找和当前节点相关的做用域中叫作username的属性。若是没找到,那就会继续向上层做用域搜索,直到根做用域。在Javascript中,这被称为原型类型的继承,子做用域以原型的形式继承自父做用域。
下面这个例子展现了应用中的做用域,它们的继承关系。
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script> <script src="script.js"></script>
<style>
.doc-example-live .ng-scope {
border: 1px dashed red; }
</style> </head> <body> <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> </body> </html>
script.js:
function EmployeeController($scope) { $scope.department = 'Engineering'; $scope.employee = { name: 'Joe the Manager', reports: [ {name: 'John Smith'}, {name: 'Mary Run'} ] }; }
注意看成用域和元素相关联的时候,AngularJS会自动给相应元素添加ng-scope类名。这个例子中的做用域范围突出显示了。子做用域的存在是颇有必要的,由于迭代器要执行{{employee.name}}
表达式,它会根据不一样的做用域生成不一样的值。一样的,{{department}}
的执行是继承自根做用域的,由于只有根做用域中定义了它。
做用域是做为$scope的数据属性关联到DOM上的,而且能在须要调试的时候被获取到。根做用关联的DOM就是ng-app指令定义的地方。通常来讲ng-app都是放在<html>
元素中的,可是也能放在其余元素中。
在控制台中想获取关联的做用域:angular.element($0).scope()
做用域中的事件传递是和DOM事件传递相似的。事件能够广播给子做用域或者传递给父做用域。举个例子:
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/angular-1.0.2.min.js"></script> <script src="script.js"></script> </head> <body> <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> //当点击此按钮时,会触发MyEvent事件,这时会把此事件也传递给父做用域,也就是Root scope,这时它的count会增长1.固然同级的Middle scope的count也会加1. <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button> //当点击此按钮时,会触发MyEvent事件,这时会把此事件传递给子做用域,也就是Leaf scope,这时它的count会增长1,固然同级的Middle scope的count也会加1. <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> </body> </html>
script.js:
function EventController($scope) { $scope.count = 0; $scope.$on('MyEvent', function() { //监听MyEvent事件 $scope.count++; }); }
浏览器接收到事件后的通常工做流程是执行一个相应的Javascript回调。回调一执行完,浏览器就会从新渲染DOM而且从新回到等待事件的状态。
当浏览器调用AngularJS上下文以外的Javascript代码时,AngularJS是不知道模型的更改的。要正确处理模型的更改,就要使用$apply方法进入AngularJS的执行上下文。只有在$apply方法内执行的模型修改才会正确地被AngularJS处理。好比,一个指令监听DOM事件,好比ng-click
,它必须在$apply方法中来执行表达式。
执行完表达式以后,$apply会进入$digest阶段。在$digest阶段,做用域会检查全部的$watch表达式,并将它们和以前的值比较。这意味着赋值语句,如$scope.username="angular"
不会立刻致使$watch被通知,取而代之的是它会等到$digest阶段才被通知。这种方式是合理的,由于它将多个模型的更新整合到一个$watch通知里,而且保证了一个$watch通知期间不会有其余一样的$watch执行。
在模板编译阶段,编译器在DOM中匹配指令。指令一般分为两种:
{{expression}}
,会用$watch来注册一个监听者。不管表达式何时改变,这类型的指令都会被通知,而且能更新视图。ng-click
,会向DOM注册一个监听者。当DOM监听者触发,指令会执行相关的表达式而且使用$apply方法更新视图。当一个外界事件(好比用户操做,计时器或者XHR)触发时,相应的表达式必须在$apply()方法内,并由其相应的做用域调用,这样全部的监听者才会被正确地更新。
大部分状况下,指令和做用域交互,不会产生新的做用域实例。可是,有些指令,好比ng-controller
和ng-repeat
会建立新的做用域,并关联到相应的DOM元素上,你可使用angular.element(aDomElement).scope()
方法来得到某一个DOM元素相关的做用域。
做用域和控制器在如下几种状况下交互:
ng-controller
)检测属性的改变是AngularJS中一项经常使用的操做,因此它应该是高效的。要注意的是,执行检测的方法不该该包含任何DOM操做,由于在Javascript对象中,DOM获取要比属性获取慢不少不少。
加油!