API Reference Scope page说:



Developer Guide Scope page说:


  • 那么,子范围是否总是从原型(prototype)上继承自其父范围?
  • 有异常(exception)吗?
  • 继承时,是否总是正常的JavaScript原型(prototype)继承?
  • 最佳答案

    快速解答:
    子作用域通常原型(prototype)地从其父作用域继承,但并非总是如此。该规则的一个异常(exception)是带有scope: { ... }的指令-这会创建一个“隔离”作用域,该作用域不原型(prototype)继承。创建“可重用组件”指令时,经常使用此构造。

    至于细微差别,作用域继承通常是直截了当的……直到您需要在子作用域中使用 2路数据绑定(bind)(即表单元素,ng-model)。如果您尝试从子作用域内部绑定(bind)到父作用域中的基本(例如,数字,字符串, bool 值),则Ng-repeat,ng-switch和ng-include可能会使您绊倒。它不能像大多数人期望的那样工作。子作用域具有其自己的属性,该属性隐藏/阴影相同名称的父属性。您的解决方法是

  • 在模型的父级中定义对象,然后在子级中引用该对象的属性:parentObj.someProp
  • 使用$ parent.parentScopeProperty(并非总是可能,但比1容易)。
  • 在父作用域上定义一个函数,并从子范围调用(并非总是可能)

  • 新的AngularJS开发人员通常不会意识到ng-repeatng-switchng-viewng-includeng-if都创建了新的子范围,因此当涉及这些指令时,常常会出现问题。 (有关此问题的快速说明,请参见this example。)

    通过遵循always have a '.' in your ng-models的“最佳实践”,可以轻松避免原语的问题-观看3分钟即可。 Misko演示了ng-switch的原始绑定(bind)问题。

    有一个 '。'在您的模型中,将确保原型(prototype)继承在起作用。因此,使用
    <input type="text" ng-model="someObj.prop1">
    
    <!--rather than
    <input type="text" ng-model="prop1">`
    -->
    

    L-o-n-g答案:

    JavaScript原型(prototype)继承

    也放在AngularJS Wiki上: https://github.com/angular/angular.js/wiki/Understanding-Scopes

    首先,必须对原型(prototype)继承有扎实的了解,尤其是如果您来自服务器端背景并且对类继承有更深的了解时,这一点很重要。因此,让我们先回顾一下。

    假设parentScope具有属性aString,aNumber,anArray,anObject和aFunction。如果childScope原型(prototype)继承自parentScope,则我们具有:

    (请注意,为了节省空间,我将anArray对象显示为具有三个值的单个蓝色对象,而不是具有三个单独的灰色文字的单个蓝色对象。)

    如果我们尝试从子作用域访问在parentScope上定义的属性,则JavaScript首先会在子作用域中查找,而不是查找该属性,然后在继承的作用域中查找并找到该属性。 (如果未在parentScope中找到该属性,它将沿原型(prototype)链继续前进,一直到根作用域为止)。所以,这些都是对的:
    childScope.aString === 'parent string'
    childScope.anArray[1] === 20
    childScope.anObject.property1 === 'parent prop1'
    childScope.aFunction() === 'parent output'
    

    假设我们然后这样做:
    childScope.aString = 'child string'
    

    不会查询原型(prototype)链,并且将新的aString属性添加到childScope。 这个新属性隐藏/阴影具有相同名称的parentScope属性。 当我们在下面讨论ng-repeat和ng-include时,这将变得非常重要。

    假设我们然后这样做:
    childScope.anArray[1] = '22'
    childScope.anObject.property1 = 'child prop1'
    

    之所以查询原型(prototype)链,是因为在childScope中找不到对象(anArray和anObject)。这些对象在parentScope中找到,并且属性值在原始对象上更新。没有向childScope添加任何新属性;没有创建新对象。 (请注意,在JavaScript中,数组和函数也是对象。)

    假设我们然后这样做:
    childScope.anArray = [100, 555]
    childScope.anObject = { name: 'Mark', country: 'USA' }
    

    不查询原型(prototype)链,子作用域将获得两个新的对象属性,这些对象属性将隐藏/隐藏具有相同名称的parentScope对象属性。

    外卖:
  • 如果我们读取childScope.propertyX,并且childScope具有propertyX,则不引用原型(prototype)链。
  • 如果设置childScope.propertyX,则不查询原型(prototype)链。

  • 最后一种情况:
    delete childScope.anArray
    childScope.anArray[1] === 22  // true
    

    我们先删除了childScope属性,然后再次尝试访问该属性时,将查询原型(prototype)链。



    角范围继承

    竞争者:
  • 以下代码创建新的作用域并进行原型(prototype)继承:ng-repeat,ng-include,ng-switch,ng-controller,带有scope: true的指令,带有transclude: true的指令。
  • 下面创建了一个新的范围,该范围不会原型(prototype)继承:带有scope: { ... }的指令。而是创建一个“隔离”范围。

  • 请注意,默认情况下,伪指令不会创建新的作用域-即默认值为scope: false

    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都会生成一个新的子作用域,该子作用域通常是从父作用域继承的。

    在第一个输入文本框中键入(例如“77”)会导致子作用域获得一个新的myPrimitive作用域属性,该属性将隐藏/阴影相同名称的父作用域属性。这可能不是您想要/期望的。

    在第二个输入文本框中键入(例如“99”)不会导致新的子属性。因为tpl2.html将模型绑定(bind)到对象属性,所以当ngModel查找对象myObject时,原型(prototype)继承就开始了-它在父作用域中找到它。

    如果我们不想将模型从原始类型更改为对象,则可以重写第一个模板以使用$ parent:
    <input ng-model="$parent.myPrimitive">
    

    在此输入文本框中键入(例如“22”)不会导致新的子属性。现在,模型已绑定(bind)到父范围的属性(因为$ parent是引用父范围的子范围属性)。

    对于所有范围(无论是否为原型(prototype)),Angular始终通过范围属性$ parent,$ childchildHead和$$ childTail跟踪父子关系(即层次结构)。我通常不会在图中显示这些范围属性。

    对于不涉及表单元素的方案,另一种解决方案是在父作用域上定义一个函数来修改基元。然后确保子级始终调用此函数,由于原型(prototype)继承,该函数将可用于子级作用域。例如。,
    // in the parent scope
    $scope.setMyPrimitive = function(value) {
         $scope.myPrimitive = value;
    }
    

    这是使用此“父函数”方法的sample fiddle。 ( fiddle 被写成这个答案的一部分:https://stackoverflow.com/a/14104318/215945。)

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

    ng开关

    ng-switch作用域继承的工作方式与ng-include一样。因此,如果需要在父作用域中与原始数据进行双向数据绑定(bind),请使用$ parent或将模型更改为对象,然后绑定(bind)至该对象的属性。这将避免子范围隐藏/隐藏父范围属性。

    另请参阅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>
    

    对于每个项目/迭代,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中,每个子范围都获得一个num属性,该属性独立于myArrayOfPrimitives数组:

    此ng-repeat无效(就像您想要/期望的那样)。在文本框中键入将更改灰色框中的值,这些值仅在子作用域中可见。我们想要的是使输入影响myArrayOfPrimitives数组,而不是子作用域原始属性。为此,我们需要将模型更改为对象数组。

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

    (我只是将一行涂成灰色,以便清楚地知道行进路线。)

    这按预期工作。键入文本框会更改灰色框中的值,这对子作用域和父作用域都是可见的。

    另请参见Difficulty with ng-model, ng-repeat, and inputs
    https://stackoverflow.com/a/13782671/215945

    ng Controller

    使用ng-controller嵌套 Controller 会导致正常的原型(prototype)继承,就像ng-include和ng-switch一样,因此适用相同的技术。
    但是,“两个 Controller 通过$ scope继承共享信息被认为是错误的形式”-http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
    应该使用服务在 Controller 之间共享数据。

    (如果您确实要通过 Controller 作用域继承共享数据,则无需执行任何操作。子作用域将有权访问所有父作用域属性。
    另请参阅Controller load order differs when loading or navigating)

    指令
  • 默认值(scope: false)-该指令不会创建新的作用域,因此此处没有继承。这很容易,但也很危险,因为,例如,一条指令可能认为它正在作用域上创建新属性,而实际上却在破坏现有属性。对于编写旨在用作可重用组件的指令,这不是一个好的选择。
  • scope: true-伪指令创建一个新的子范围,该子范围从原型(prototype)上继承自父范围。如果一个以上的指令(在同一DOM元素上)请求一个新范围,则仅创建一个新的子范围。由于我们具有“常规”原型(prototype)继承,因此就像ng-include和ng-switch一样,因此请谨慎使用2路数据绑定(bind)到父作用域原语,以及子作用域隐藏/遮蔽父作用域属性。
  • scope: { ... }-指令创建一个新的隔离/隔离范围。它不是原型(prototype)继承的。在创建可重用组件时,这通常是您的最佳选择,因为该指令不会意外读取或修改父作用域。但是,此类指令通常需要访问一些父范围属性。对象哈希用于在父作用域和隔离作用域之间建立双向绑定(bind)(使用'=')或单向绑定(bind)(使用'@')。还有“&”绑定(bind)到父作用域表达式。因此,所有这些都创建了从父范围派生的本地范围属性。
    请注意,属性用于帮助设置绑定(bind)-您不仅可以在对象哈希中引用父作用域属性名称,还必须使用属性。例如,如果您要绑定(bind)到隔离范围parentProp<div my-directive>的父属性scope: { localProp: '@parentProp' },则此方法将无效。必须使用一个属性来指定指令要绑定(bind)到的每个父属性:<div my-directive the-Parent-Prop=parentProp>scope: { localProp: '@theParentProp' }
    隔离范围的__proto__引用对象。
    隔离作用域的$ parent引用父作用域,因此,尽管它是隔离的并且不从父作用域继承原型(prototype),但它仍然是子作用域。
    对于下面的图片,我们有
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }另外,假设该指令在其链接函数中执行此操作:scope.someIsolateProp = "I'm isolated"有关隔离范围的更多信息,请参见http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  • transclude: true-伪指令创建一个新的“已包含”子作用域,该子作用域典型地从父作用域继承。超越范围和隔离范围(如果有的话)是 sibling -每个范围的$ parent属性引用相同的父范围。当同时存在一个已包含的范围和一个隔离范围时,隔离范围属性$$ nextSibling将引用该已包含的范围。我不知道这个被包含在范围之外的细微差别。
    对于下面的图片,假定与上面相同的指令并添加以下内容:transclude: true

  • fiddle具有showScope()函数,可用于检查隔离和超越的作用域。请参阅 fiddle 中注释中的说明。

    概要

    范围有四种类型:
  • 正常原型(prototype)作用域继承-ng-include,ng-switch,ng-controller,带有scope: true的指令
  • 具有复制/赋值的正常原型(prototype)作用域继承-ng-repeat。 ng-repeat的每次迭代都会创建一个新的子范围,并且该新的子范围始终会获得一个新的属性。
  • 隔离范围-使用scope: {...}的指令。这不是原型(prototype),但是'=','@'和'&'提供了一种通过属性访问父范围属性的机制。
  • 超越范围-带有transclude: true的指令。这也是正常的原型(prototype)作用域继承,但它也是任何孤立作用域的同级对象。

  • 对于所有范围(无论是原型(prototype)还是非原型(prototype)),Angular始终通过属性$ parent和$$ childHead和$$ childTail跟踪父子关系(即层次结构)。

    使用graphviz上的github“* .dot”文件生成了图表。蒂姆·卡斯韦尔(T​​im Caswell)的“Learning JavaScript with Object Graphs”是使用GraphViz绘制图表的灵感。

    07-24 09:46
    查看更多