问题

API参考范围页面说:

A scope can inherit from a parent scope.

开发人员指南范围页面说:

A scope (prototypically) inherits properties from its parent scope.

那么,子范围总是从原型继承自父范围吗?有例外吗?当它继承时,它是否总是正常的JavaScript原型继承?



解决方法

快速回答:
子范围通常从其父范围继承,但不总是.此规则的一个例外是带有 scope的指令:{...} - 这创建了一个"原型"继承的"isolation"作用域.在创建"可重用组件"伪指令时,通常使用此构造.

至于细微差别,范围继承通常是直接的...直到你需要 2-way数据绑定(即表单元素,ng-model)在子范围.如果您尝试在子范围内从父范围绑定到原始(例如,数字,字符串,布尔值),则ng-repeat,ng-switch和ng-它不工作的方式大多数人期望它应该工作.子范围获取自己的属性,隐藏/隐藏同名的父属性.您的解决方法是

  1. define objects in the parent for your model, then reference a property of that object in the child: parentObj.someProp
  2. use $parent.parentScopeProperty (not always possible, but easier than 1. where possible)
  3. define a function on the parent scope, and call it from the child (not always possible)


L-o-n-g answer:

JavaScript Prototypal Inheritance

也放在了AngularJS维基上: 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对象属性.

更多属性隐藏

Takeaways:

  • If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
  • If we set childScope.propertyX, the prototype chain is not consulted.

最后一个方案:

delete childScope.anArray
childScope.anArray[1] === 22  // true

我们首先删除了childScope属性,然后当我们尝试再次访问该属性时,会查询原型链.

删除子属性后


Angular Scope Inheritance

竞争者:

  • The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-controller, directive with scope: true, directive with transclude: true.
  • The following creates a new scope which does not inherit prototypically: directive with scope: { ... }. This creates an "isolate" scope instead.

请注意,默认情况下,指令不会创建新作用域,即默认值为 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都会生成一个新的子范围,它从父范围原型继承.

ng-include child scopes

在第一个输入文本框中键入(例如"77")会导致子范围获得一个新的隐藏/隐藏同名的父范围属性的 myPrimitive scope属性.这可能不是你想要的/期望的.

ng-include with a primitive

在第二个输入文本框中键入(例如"99")不会产生新的子属性.因为tpl2.html将模型绑定到对象属性,当ngModel查找对象myObject时,原型继承会发生.它会在父作用域中找到它.

ng-include与对象

如果我们不想将模型从基本类型更改为对象,我们可以重写第一个模板以使用$ parent:

<input ng-model="$parent.myPrimitive">

在此输入文本框中键入(例如,"22")不会产生新的子属性.该模型现在绑定到父作用域的属性(因为$ parent是引用父作用域的子作用域属性).

ng-include with $ parent

对于所有范围(原型或非原型),Angular总是通过范围属性$ parent,$$ childHead和$$ childTail跟踪父子关系(即层次结构).我通常不在图表中显示这些范围属性.

对于不涉及表单元素的情况,另一个解决方案是在父作用域上定义一个函数来修改该基元.然后确保孩子总是调用这个函数,这将由于原型继承而可用于子范围.例如

// in the parent scope
$scope.setMyPrimitive = function(value) {
     $scope.myPrimitive = value;
}

以下是使用此"父功能"方法的示例小提琴. (小提琴是作为此答案的一部分写成的: http://stackoverflow.com/a/14104318/215945 .)

另请参阅 http://stackoverflow.com/a/13782671/215945 https://github.com/angular/angular.js/issues/1267 .

ng-switch

ng-switch范围继承的工作原理与ng-include类似.因此,如果您需要双向数据绑定到父作用域中的基元,请使用$ parent,或将模型更改为对象,然后绑定到该对象的属性.这将避免父作用域属性的子作用域隐藏/阴影.

另请参见 AngularJS,交换机的绑定范围?

ng-repeat

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 with primitives

这个ng-repeat不会工作(像你想要的/期望的).输入到文本框中会更改灰色框中的值,这些值仅在子范围中可见.我们想要的是输入影响myArrayOfPrimitives数组,而不是子scope的基元属性.要实现这一点,我们需要将模型更改为对象数组.

所以,如果item是一个对象,对原始对象(而不是副本)的引用被分配给新的子范围属性.更改子作用域属性的值(即,使用ng-model,因此 obj.num )确实更改父作用域引用的对象.因此,在上面的第二个ng-repeat中,我们有:

ng-repeat with objects

(我将一行灰色,这样它就清楚了它的前进方向.)

这个工作原理.输入文本框将更改灰色框中的值,这些值对子范围和父范围都是可见的.

另请参阅难以使用ng-model,ng-repeat和输入 http://stackoverflow.com/a/13782671/215945

ng-controller

使用ng-controller的嵌套控制器会产生正常的原型继承,就像ng-include和ng-switch一样,所以使用相同的技术. 但是,"它被认为是两个控制器通过$范围继承共享信息的不良形式" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 应使用服务来在控制器之间共享数据.

(如果您真的想通过控制器范围继承来共享数据,那么您无需执行任何操作.子范围将可以访问所有父范围属性. 另请参见加载或导航控制器加载顺序时, a>)

directives

  1. default (scope: false) - the directive does not create a new scope, so there is no inheritance here. This is easy, but also dangerous because, e.g., a directive might think it is creating a new property on the scope, when in fact it is clobbering an existing property. This is not a good choice for writing directives that are intended as reusable components.
  2. scope: true - the directive creates a new child scope that prototypically inherits from the parent scope. If more than one directive (on the same DOM element) requests a new scope, only one new child scope is created. Since we have "normal" prototypal inheritance, this is like ng-include and ng-switch, so be wary of 2-way data binding to parent scope primitives, and child scope hiding/shadowing of parent scope properties.
  3. 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 directives 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' }.
    Isolate scope's __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.
    For the picture below we have
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> and
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    Also, assume the directive does this in its linking function: scope.someIsolateProp = "I'm isolated"
    isolated scope
    For more information on isolate scopes see
    http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true - the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. The transcluded and the isolated scope (if any) are siblings -- the $parent property of each scope references the same parent scope. When a transcluded and an isolate scope both exist, isolate scope property $$nextSibling will reference the transcluded scope. I'm not aware of any nuances with the transcluded scope.
    For the picture below, assume the same directive as above with this addition: transclude: true
    transcluded scope

小提琴有一个 showScope()函数,可以使用检查隔离和转录的范围.请参阅小提琴评论中的说明.


Summary

有四种类型的范围:

  1. normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
  2. normal prototypal scope inheritance with a copy/assignment -- ng-repeat. Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
  3. isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '@', and '&' provide a mechanism to access parent scope properties, via attributes.
  4. transcluded scope -- directive with transclude: true. This one is also normal prototypal scope inheritance, but it is also a sibling of any isolate scope.

对于所有范围(原型或非原型),Angular始终通过属性$ parent和$$ childHead和$$ childTail跟踪父子关系(即层次结构).

图表是使用"*生成的.点"文件,位于 github 上. Tim Caswell的"学习JavaScript对象图"是使用GraphViz的图表的灵感.




相关问题推荐