转自:http://www.lovelucy.info/understanding-scopes-in-angularjs.html
angularjs 中的scope继承关系
ng-include
假设在我们的 controller 中,
$scope.myPrimitive = 50; $scope.myObject = {aNumber: 11}; |
HTML 为:
<script type="text/ng-template" id="/tpl1.html"> <input ng-model="myPrimitive"> </script> <div ng-include src="‘/tpl1.html‘"></div> <script type="text/ng-template" id="/tpl2.html"> <input ng-model="myObject.aNumber"> </script> <div ng-include src="‘/tpl2.html‘"></div> |
每一个 ng-include 会生成一个子 Scope,每个子 Scope 都继承父 Scope。
输入(比如”77″)到第一个 input 文本框,则子 Scope 将获得一个新的 myPrimitive 属性,覆盖掉父 Scope 的同名属性。这可能和你预想的不一样。
输入(比如”99″)到第二个 input 文本框,并不会在子 Scope 创建新的属性,因为 tpl2.html 将 model 绑定到了一个对象属性(an object property),原型继承在这时发挥了作用,ngModel 寻找对象 myObject 并且在它的父 Scope 中找到了。
如果我们不想把 model 从 number 基础类型改为对象,我们可以用 $parent 改写第一个模板:
<input ng-model="$parent.myPrimitive"> |
输入(比如”22″)到这个文本框也不会创建新属性了。model 被绑定到了父 scope 的属性上(因为 $parent 是子 Scope 指向它的父 Scope 的一个属性)。
对于所有的 scope (原型继承的或者非继承的),Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系(也就是继承关系),图中为简化而未画出这些属性。
在没有表单元素的情况下,另一种方法是在父 Scope 中定义一个函数来修改基本数据类型。因为有原型继承,子 Scope 确保能够调用这个函数。例如,
// 父 Scope 中 $scope.setMyPrimitive = function(value) { $scope.myPrimitive = value; } |
查看 DEMO。参考 StackOverflow。
ng-switch
ng-switch 的原型继承和 ng-include 一样。所以如果你需要对基本类型数据进行双向绑定,使用 $parent,或者将其改为 object 对象并绑定到对象的属性,防止子 Scope 覆盖父 Scope 的属性。
参考 AngularJS, bind scope of a switch-case?
ng-repeat
ng-repeat 有一点不一样。假设在我们的 controller 里:
$scope.myArrayOfPrimitives = [ 11, 22 ]; $scope.myArrayOfObjects = [{num: 101}, {num: 202}] |
还有 HTML:
<ul> <li ng-repeat="num in myArrayOfPrimitives"> <input ng-model="num"> </li> <ul> <ul> <li ng-repeat="obj in myArrayOfObjects"> <input ng-model="obj.num"> </li> <ul> |
对于每一个 Item,ng-repeat 创建新的 Scope,每一个 Scope 都继承父 Scope,但同时 item 的值也被赋给了新 Scope 的新属性(新属性的名字为循环的变量名)。Angular ng-repeat 的源码实际上是这样的:
childScope = scope.$new(); // 子 scope 原型继承父 scope ... childScope[valueIdent] = value; // 创建新的 childScope 属性 |
如果 item 是一个基础数据类型(就像 myArrayOfPrimitives),本质上它的值被复制了一份赋给了新的子 scope 属性。改变这个子 scope 属性值(比如用 ng-model,即 num
)不会改变父 scope 引用的 array。所以上面第一个 ng-repeat 里每一个子 scope 获得的 num
属性独立于 myArrayOfPrimitives 数组:
这样的 ng-repeat 和你预想中的不一样。在 Angular 1.0.2 及更早的版本,向文本框中输入会改变灰色格子的值,它们只在子 Scope 中可见。Angular 1.0.3+ 以后,输入文本不会再有任何作用了。(参考StackOverflow 上的解释)我们希望的是输入能改变 myArrayOfPrimitives 数组,而不是子 Scope 里的属性。为此我们必须将 model 改为一个关于对象的数组(array of objects)。
所以如果 item 是一个对象,则对于原对象的一个引用(而非拷贝)被赋给了新的子 Scope 属性。改变子 Scope 属性的值(使用 ng-model,即 obj.num)也就改变了父 Scope 所引用的对象。所以上面第二个 ng-repeat 可表示为:
这才是我们想要的。输入到文本框即会改变灰色格子的值,该值在父 Scope 和子 Scope 均可见。
总结
一共有四种 Scope:
- 普通进行原型继承的 Scope —— ng-include, ng-switch, ng-controller, directive with
scope: true
- 普通原型继承的 Scope 但拷贝赋值 —— ng-repeat。 每个 ng-repeat 的循环都创建新的子 Scope,并且子 Scope 总是获得新的属性。
- 独立的 isolate scope —— directive with
scope: {...}
。它不是原型继承,但 ‘=’, ‘@’ 和 ‘&’ 提供了访问父 Scope 属性的机制。 - transcluded scope —— directive with
transclude: true
。它也遵循原型继承,但它同时是任何 isolate scope 的兄弟。
对于所有的 Scope,Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系。