A scope can inherit from a parent scope.
A scope (prototypically) inherits properties from its parent scope.
那么,子范围总是从原型继承自父范围吗?有例外吗?当它继承时,它是否总是正常的JavaScript原型继承?
子范围通常从其父范围继承,但不总是。此规则的一个例外是具有范围的指令:{…} – 这创建了一个“原型”继承的“isolation”范围。当创建“可重用组件”伪指令时,通常使用此构造。
至于细微差别,范围继承通常是直接的…直到你需要在子范围中的双向数据绑定(即表单元素,ng-model)。如果您尝试在子范围内从父范围绑定到一个图元(例如,数字,字符串,布尔值),Ng-repeat,ng-switch和ng-它不工作的方式大多数人期望它应该工作。子范围获取其自己的属性,隐藏/阴影同名的父属性。您的解决方法是
>在您的模型的父代中定义对象,然后在子代:parentObj.someProp中引用该对象的属性
> use $ parent.parentScopeProperty(不总是可能的,但是尽可能比1.容易)
>在父作用域上定义一个函数,并从子作用域调用它(不总是可能)
L-o-n-g答案:
JavaScript原型继承
也放在AngularJS wiki:https://github.com/angular/angular.js/wiki/Understanding-Scopes
重要的是首先对原型继承有一个坚实的理解,特别是如果你来自服务器端背景,并且你更熟悉类继承。所以让我们先看看。
假设parentScope具有属性aString,aNumber,anArray,anObject和aFunction。如果childScope从parentScope原型继承,我们有:
(请注意,为了节省空间,我将anArray对象显示为具有三个值的单个蓝色对象,而不是具有三个单独灰色文本的单个蓝色对象。)
如果我们尝试从子范围访问parentScope上定义的属性,JavaScript将首先查找子范围,找不到属性,然后查找继承的范围,并找到属性。 (如果没有在parentScope中找到属性,它将继续向上的原型链…一直到根范围)。所以,这些都是真实的:
childScope.aString === 'parent string' childScope.anArray[1] === 20 childScope.anObject.property1 === 'parent prop1' childScope.aFunction() === 'parent output'
假设我们这样做:
childScope.aString = 'child string'
未咨询原型链,并且将新的aString属性添加到childScope。此新属性使用相同的名称隐藏/隐藏parentScope属性。当我们在下面讨论ng-repeat和ng-include时,这将变得非常重要。
假设我们这样做:
childScope.anArray[1] = '22' childScope.anObject.property1 = 'child prop1'
参考原型链是因为在childScope中找不到对象(anArray和anObject)。对象在parentScope中找到,并且属性值在原始对象上更新。没有新的属性添加到childScope;不会创建新对象。 (请注意,在JavaScript数组和函数也是对象。)
假设我们这样做:
childScope.anArray = [100,555] childScope.anObject = { name: 'Mark',country: 'USA' }
未咨询原型链,子作用域将获取两个新的对象属性,以使用相同的名称隐藏/隐藏parentScope对象属性。
外卖:
>如果我们读childScope.propertyX,并且childScope有propertyX,那么不参考原型链。
>如果我们设置childScope.propertyX,则不参考原型链。
最后一个场景:
delete childScope.anArray childScope.anArray[1] === 22 // true
我们首先删除了childScope属性,然后当我们再次访问该属性时,会查询原型链。
角度范围继承
竞争者:
>以下创建新作用域,并原型继承:ng-repeat,ng-include,ng-switch,ng-controller,指令,范围:true,directive和transclude:true。
>以下代码创建了一个不继承原型的新范围:directive with scope:{…}。这将创建一个“隔离”范围。
注意,默认情况下,指令不创建新范围 – 即,默认值为scope:false。
ng-include
假设我们在我们的控制器:
$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生成一个新的子范围,它从父范围原型继承。
在第一个输入文本框中键入(例如“77”)将导致子范围获得一个新的myPrimitive scope属性,该属性隐藏/隐藏同一名称的父作用域属性。这可能不是你想要/期望。
在第二个输入文本框中键入(例如“99”)不会产生新的子属性。因为tpl2.html将模型绑定到对象属性,当ngModel查找对象myObject时,原型继承会发生 – 它在父作用域中找到它。
我们可以重写第一个模板以使用$ parent,如果我们不想将我们的模型从一个基本类型更改为一个对象:
<input ng-model="$parent.myPrimitive">
在此输入文本框中键入(例如,“22”)不会产生新的子属性。该模型现在绑定到父作用域的属性(因为$ parent是引用父作用域的子作用域属性)。
对于所有范围(原型或非原型),Angular总是通过范围属性$ parent,$$ childHead和$$ childTail跟踪父子关系(即层次结构)。我通常不在图中显示这些范围属性。
对于不涉及表单元素的情况,另一个解决方案是在父作用域上定义一个函数来修改该基元。然后确保孩子总是调用这个函数,这将由于原型继承而可用于子范围。例如。,
// in the parent scope $scope.setMyPrimitive = function(value) { $scope.myPrimitive = value; }
这里是一个使用这种“父函数”方法的sample fiddle。 (小提琴是作为这个答案的一部分写的:http://stackoverflow.com/a/14104318/215945.)
参见http://stackoverflow.com/a/13782671/215945和https://github.com/angular/angular.js/issues/1267。
ng开关
ng-switch范围继承的工作原理与ng-include类似。因此,如果您需要双向数据绑定到父作用域中的基元,请使用$ parent,或将模型更改为对象,然后绑定到该对象的属性。这将避免父作用域属性的子作用域隐藏/阴影。
参见AngularJS,bind scope of a switch-case?
ng重复
Ng-repeat的工作方式略有不同。假设我们在我们的控制器:
$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>
对于每个项目/迭代,ng-repeat创建一个新的范围,它从父范围原型继承,但它还将项目的值分配给新子范围上的一个新属性。 (新属性的名称是循环变量的名称。)这里的ng-repeat的Angular源代码实际上是:
childScope = scope.$new(); // child scope prototypically inherits from parent scope ... childScope[valueIdent] = value; // creates a new childScope property
如果item是一个基本类型(如在myArrayOfPrimitives中),则基本上将该值的副本分配给新的子范围属性。更改子作用域属性的值(即,使用ng-model,因此子作用域num)不会更改父作用域引用的数组。因此,在上面的第一个ng-repeat中,每个子范围获取一个独立于myArrayOfPrimitives数组的num属性:
这ng-repeat不会工作(像你想要的/期望它)。输入到文本框中会更改灰色框中的值,这些值仅在子范围中可见。我们想要的是输入影响myArrayOfPrimitives数组,而不是子scope的基元属性。要实现这一点,我们需要将模型更改为对象数组。
因此,如果item是一个对象,则对该新对象(非副本)的引用将分配给新的子scope。更改子作用域属性的值(即使用ng-model,因此obj.num)会更改父作用域引用的对象。因此,在上面的第二个ng-repeat中,我们有:
(我上色一行灰色,以便它清楚它在哪里)。
这按预期工作。输入文本框将更改灰色框中的值,这些值对子范围和父范围都可见。
另见Difficulty with ng-model,ng-repeat,and inputs和
http://stackoverflow.com/a/13782671/215945
ng控制器
使用ng-controller的嵌套控制器导致正常的原型继承,就像ng-include和ng-switch一样,所以使用相同的技术。
然而,“它被认为是两个控制器通过$范围继承共享信息的不良形式” – http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
应该使用服务来在控制器之间共享数据。
(如果你真的想通过控制器范围继承来共享数据,那么你不需要做任何事情。子范围将有权访问所有的父范围属性。
参见Controller load order differs when loading or navigating)
指令
> default(scope:false) – 指令不创建一个新的作用域,所以这里没有继承。这很容易,但也很危险,因为,例如,一个指令可能认为它正在范围上创建一个新的属性,而事实上它正在破坏一个现有的属性。这不是编写用作可重用组件的指令的好选择。
> scope:true – 指令创建一个从父范围原型继承的新子范围。如果多个指令(在同一个DOM元素上)请求一个新的作用域,只会创建一个新的子作用域。因为我们有“正常”的原型继承,这就像ng-include和ng-switch,所以要注意双向数据绑定到父镜像原型,以及子镜像隐藏/镜像父镜像属性。
> scope:{…} – 该伪指令创建一个新的隔离/隔离范围。它不原型继承。这通常是创建可重用组件时的最佳选择,因为该伪指令不能意外读取或修改父作用域。然而,这样的指令通常需要访问一些父范围属性。对象哈希用于在父作用域和隔离作用域之间设置双向绑定(使用’=’)或单向绑定(使用’@’)。还有’&’绑定到父作用域表达式。因此,这些都创建从父作用域派生的本地作用域属性。
请注意,属性用于帮助设置绑定 – 您不能仅在对象哈希中引用父作用域属性名称,您必须使用属性。例如,如果你想绑定到父属性parentProp在孤立的作用域,这将不工作:
隔离作用域的__proto__引用对象。
Isolate scope的$ parent引用父范围,因此虽然它是孤立的,并且不从父范围继承原型,但它仍然是一个子范围。
对于下面的图片我们有
和
范围:{interpolatedProp:’@interpolated’,twowayBindingProp:’= twowayBinding’}
此外,假设指令在其链接函数中执行此操作:scope.someIsolateProp =“I’m isolated”
有关隔离范围的更多信息,请参阅 http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
> transclude:true – 该伪指令创建一个新的“转录”子范围,它从父范围继承。被转换和被隔离的范围(如果有的话)是同级 – 每个范围的$ parent属性引用同一个父级范围。当转录和隔离范围都存在时,隔离范围属性$$ nextSibling将引用转录的范围。我不知道与转录范围有任何细微差别。
对于下面的图片,假设与上面相同的指令,添加:transclude:true
这个fiddle有一个showScope()函数,可用于检查隔离和转录的作用域。请参阅小提琴评论中的说明。
概要
有四种类型的范围:
>正常原型范围继承 – ng-include,ng-switch,ng-controller,指令范围:true
>正常原型范围继承与拷贝/分配 – 重复。 ng-repeat的每次迭代创建一个新的子范围,并且新的子范围总是获得一个新的属性。
> isolation scope – 带范围的指令:{…}。这个不是原型的,但是’=’,’@’和’&’提供通过属性访问父作用域属性的机制。
> transcluded scope – 使用transclude:true的指令。这一个也是正常的原型范围继承,但它也是任何孤立范围的兄弟。
对于所有范围(原型或非原型),Angular总是通过属性$ parent和$$ childHead和$$ childTail跟踪父子关系(即层次结构)。
图表是使用github上的graphviz“* .dot”文件生成的。Tim Caswell的“Learning JavaScript with Object Graphs”是使用GraphViz作为图表的灵感。
本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:AngularJS中范围原型/原型继承的细微差别是什么?