The API Reference Scope page says:
作用域can继承自父作用域.
作用域(原型)从其父作用域继承属性.
- 那么,子作用域总是从父作用域继承原型吗?
- 有例外吗?
- 当它确实继承时,它总是正常的JavaScript原型继承吗?
The API Reference Scope page says:
作用域can继承自父作用域.
作用域(原型)从其父作用域继承属性.
Quick answer:
至于细微差别,范围继承通常是直截了当的...直到子范围中需要2-way data binding(即表单元素、ng模型).如果试图从子作用域内部绑定到父作用域中的primitive(例如,数字、字符串、布尔值),则Ng repeat、Ng switch和Ng include可能会使您出错.它不像大多数人期望的那样工作.子作用域获取自己的属性,该属性隐藏/隐藏同名的父属性.你的变通办法是
新的AngularJS开发人员通常没有意识到ng重复
、ngswitch
、ng-view
、ng包括
和ng-if
都会创建新的子作用域,因此当涉及这些指令时,问题往往会出现.(有关问题的快速说明,请参见this example.)
通过遵循always have a '.' in your ng-models-观看3分钟的"最佳实践",可以很容易地避免原语的这个问题.Misko演示了ngswitch
的原始绑定问题.
有一个"."在您的模型中,将确保原型继承发挥作用.所以,使用
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
Also placed on the AngularJS wiki: https://github.com/angular/angular.js/wiki/Understanding-Scopes
首先要扎实理解原型继承,特别是如果您来自服务器端背景并且更熟悉类继承,这一点很重要.那么让我们先来回顾一下这一点.
假设parentScope具有aString、aNumber、anArray、anObject和aFunction属性.如果childScope原型继承自parentScope,我们有:
(请注意,为了节省空间,我将anArray
对象显示为带有三个值的单个蓝色对象,而不是带有三个单独灰色文字的单个蓝色对象.)
如果我们try 从子范围访问在parentScope上定义的属性,JavaScript将首先在子范围内查找,而不是找到该属性,然后在继承范围内查找并找到该属性.(如果它没有在parentScope中找到属性,它将继续沿着原型链向上.一直到根作用域).所以,这些都是真的:
childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'
假设我们这样做:
childScope.aString = 'child string'
不参考原型链,并向childScope添加了一个新的aString属性.This new property hides/shadows the parentScope property with the same name.当我们在下面讨论ng repeat和ng include时,这将变得非常重要.
假设我们这样做:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
由于在childScope中找不到对象(anArray和anObject),因此会参考prototype链.这些对象位于parentScope中,属性值在原始对象上更新.未向childScope添加新属性;不会创建新对象.(请注意,在JavaScript中,数组和函数也是对象.)
假设我们这样做:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
不参考原型链,子作用域获得两个新的对象属性,它们隐藏/隐藏具有相同名称的父作用域对象属性.
外卖:
最后一个场景:
delete childScope.anArray
childScope.anArray[1] === 22 // true
我们首先删除了childScope属性,然后当我们再次try 访问该属性时,会参考原型链.
竞争者:
scope: true
的指令、带transclude: true
的指令.scope: { ... }
的指令.这会创建一个"隔离"范围.注意,缺省情况下,指令不会创建新的作用域--即缺省值为scope: false
.
假设我们的控制器中有:
$scope.myPrimitive = 50;
$scope.myObject = {aNumber: 11};
在我们的HTML中:
<script type="text/ng-template" id="/tpl1.html">
<input ng-model="myPrimitive">
</script>
<div ng包括 src="'/tpl1.html'"></div>
<script type="text/ng-template" id="/tpl2.html">
<input ng-model="myObject.aNumber">
</script>
<div ng包括 src="'/tpl2.html'"></div>
每个ng include生成一个新的子作用域,该子作用域通常从父作用域继承.
在第一个输入文本框中键入(比方说,"77")会导致子范围获得一个新的myPrimitive
范围属性,该属性隐藏/隐藏同名的父范围属性.这可能不是您想要/期望的.
在第二个输入文本框中键入(例如,"99")不会产生新的子属性.因为tpl2.html将模型绑定到一个对象属性,当ngModel查找对象myObject时,原型继承开始生效——它在父作用域中找到对象myObject.
如果我们不想将模型从基本体更改为对象,可以重写第一个模板,使用$parent:
<input ng-model="$parent.myPrimitive">
在此输入文本框中键入(比方说,"22")不会产生新的子属性.模型现在绑定到父作用域的属性(因为$Parent是引用父作用域的子作用域属性).
对于所有作用域(无论是否为原型),ANGLE始终通过作用域属性$Parent、$$Child Head和$$ChildTail跟踪父子关系(即层次 struct ).我通常不会在图表中显示这些作用域属性.
对于不涉及表单元素的场景,另一种解决方案是在父范围上定义一个函数来修改原语.然后确保子级始终调用此函数,由于原型继承,该子范围可以使用该函数.例如,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
下面是一个使用这种"父函数"方法的sample fiddle.(这把小提琴是作为这个答案的一部分写的:https://stackoverflow.com/a/14104318/215945.)
另请参见https://stackoverflow.com/a/13782671/215945和https://github.com/angular/angular.js/issues/1267.
ng switch作用域继承的工作原理与ng include一样.因此,如果需要将双向数据绑定到父范围中的原语,请使用$parent,或者将模型更改为对象,然后绑定到该对象的属性.这将避免子范围隐藏/隐藏父范围属性.
另见第AngularJS, bind scope of a switch-case?页
Ng-repeat works a little differently. 假设我们的控制器中有:
$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects = [{num: 101}, {num: 202}]
在我们的HTML中:
<ul><li ng重复="num in myArrayOfPrimitives">
<input ng-model="num">
</li>
<ul>
<ul><li ng重复="obj in myArrayOfObjects">
<input ng-model="obj.num">
</li>
<ul>
对于每个项目/迭代,ng repeat都会创建一个新的范围,该范围通常从父范围but it also assigns the item's value to a new property on the new child scope继承.(新属性的名称是循环变量的名称.)以下是ng repeat的Angular源代码:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
如果项是基元(如myArrayOfPrimitives中的基元),则该值的一个副本本质上被分配给新的子范围属性.更改子作用域属性的值(即,使用ng模型,因此子作用域为num
)会更改父作用域引用的array.因此,在上面的第一次ng重复中,每个子作用域都会获得一个独立于myArrayOfPrimitives数组的num
属性:
这种重复不起作用(就像你希望/期望的那样).在文本框中键入会更改灰色框中的值,这些值仅在子作用域中可见.我们想要的是输入影响myArrayOfPrimitives数组,而不是子范围原语属性.要实现这一点,我们需要将模型更改为对象array.
因此,如果项是对象,则对原始对象(而不是副本)的引用将指定给新的子范围属性.更改子范围属性的值(即,使用ng模型,因此为obj.num
)does更改父范围引用的对象.在上面的第二次ng重复中,我们有:
(我把一条线涂成灰色,这样就可以清楚地知道它要go 哪里.)
这是意料之中的事.在文本框中键入会更改灰色框中的值,这些值对子作用域和父作用域都可见.
另见Difficulty with ng-model, ng重复, and inputs和
使用ng-Controller的嵌套控制器会导致正常的原型继承,就像ng包括和ngswitch 一样,因此同样的技术也适用. 但是,"两个控制器通过$Scope继承共享信息被认为是不好的形式"--http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 应该使用服务在控制器之间共享数据.
(如果您确实希望通过控制器作用域继承共享数据,则无需执行任何操作.子作用域将可以访问所有父作用域属性.
scope: false
)-该指令不创建新的作用域,因此这里没有继承.这很容易,但也很危险,因为例如,指令可能认为它正在范围上创建一个新属性,而实际上它正在 destruct 一个现有属性.对于编写作为可重用组件的指令来说,这不是一个好的 Select .scope: true
-该指令创建一个新的子作用域,该子作用域通常继承自父作用域.如果多个指令(在同一个DOM元素上)请求新的作用域,则只创建一个新的子作用域.因为我们有"正常"的原型继承,这类似于ng包括和ngswitch ,所以要小心父作用域原语的双向数据绑定,以及父作用域属性的子作用域隐藏/隐藏.scope: { ... }
- the directive creates a new isolate/isolated scope. It does not prototypically inherit. This is usually your best choice when creating reusable components, since the directive cannot accidentally read or modify the parent scope. However, such 指令 often need access to a few parent scope properties. The object hash is used to set up two-way binding (using '=') or one-way binding (using '@') between the parent scope and the isolate scope. There is also '&' to bind to parent scope expressions. So, these all create local scope properties that are derived from the parent scope.
Note that attributes are used to help set up the binding -- you can't just reference parent scope property names in the object hash, you have to use an attribute. E.g., this won't work if you want to bind to parent property parentProp
in the isolated scope: <div my-directive>
and scope: { localProp: '@parentProp' }
. An attribute must be used to specify each parent property that the directive wants to bind to: <div my-directive the-Parent-Prop=parentProp>
and scope: { localProp: '@theParentProp' }
.
__proto__
references Object.
Isolate scope's $parent references the parent scope, so although it is isolated and doesn't inherit prototypically from the parent scope, it is still a child scope.
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
and
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
scope.someIsolateProp = "I'm isolated"
transclude: true
-该指令创建一个新的"转置"子作用域,该子作用域通常从父作用域继承.被转移的作用域和隔离的作用域(如果有的话)是同级的——每个作用域的$parent属性都引用相同的父作用域.当转移范围和隔离范围都存在时,隔离范围属性$$nextSibling将引用转移范围.我不知道这个隐蔽的范围有什么细微差别.该fiddle具有可用于判断隔离和转移范围的showScope()
功能.请参阅小提琴中注释中的说明.
有四种类型的作用域:
scope: true
的指令scope: {...}
.这不是原型,但是‘=’、‘@’和‘&;’提供了一种通过属性访问父作用域属性的机制.transclude: true
.这也是正常的原型作用域继承,但它也是任何孤立作用域的同级.对于所有作用域(原型或非原型),Angular始终通过属性$parent和$$childHead和$$childTail跟踪父子关系(即层次 struct ).
Diagrams were generated with graphviz "*.dot" files, which are on github. Tim Caswell's "Learning JavaScript with Object Graphs" was the inspiration for using GraphViz for the diagrams.