prototypal-inheritance - angularj作用域原型/ 原型继承的细微差别是什么?

  显示原文与译文双语对照的内容

API引用作用域页面 。says说明:

一个范围从父作用域继承。

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

作用域( prototypically ) 从其父作用域继承属性。

那么,子作用域总是从其父作用域继承? 是否有例外当它确实继承时,是否总是普通的JavaScript Prototype继承?

时间:

快速应答:
子作用域通常从其父作用域继承,但不总是。 这里规则的一个例外是使用 scope: {.. . } --这将创建一个不继承继承的"隔离"作用域。 创建"可以重用组件"指令时通常使用这里构造。

至于细微差别,范围继承通常是 straightfoward 。 在子作用域中需要 2-way数据绑定 ( 例如 ,表单元素,ng-model ) 。 Ng-repeat 、ng-switch ng-include旅行你如果你可以尝试绑定到一个原始 ( e.g,,字符串,布尔值) 在父范围从孩子范围。 它不能像大多数人预期的那样工作。 子作用域获取自己的属性,它隐藏/隐藏相同名称的父属性。 你的工作方式是

  1. 在模型中为模型定义对象,然后在子对象中引用该对象的属性: parentObj.someProp
  2. 使用 $parent.parentScopeProperty ( 不总是可能,但比 1容易) 。 如果可能的话)
  3. 在父作用域上定义一个函数,并从子作用域调用它( 不总是可能)

L-o-n-g应答:

JavaScript Prototype继承

在AngularJS的wiki上还放置了英镑: https://github.com/angular/angular.js/wiki/Understanding-Scopes

首先对Prototype继承有一个坚实的理解是很重要的,特别是当你来自一个server-side背景并且你是failiar继承的更多。 我们先回顾一下。

假设parentScope有属性 aString,数字,anArray,anObject和 aFunction 。 如果 childScope prototypically从parentScope继承,我们有:

prototypal inheritance

( 注意,为了节省空间,我将 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如下。

property hiding

假设我们这样做:


childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'

原型链是咨询,因为对象( anArray和 anObject ) childScope中发现。 对象在parentScope中找到,属性值在原始对象上更新。 不向childScope添加新属性;不创建新对象。 ( 请注意,在JavaScript数组和函数中也是对象。)

follow the prototype chain

假设我们这样做:


childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

不查阅Prototype链,子作用域获取两个新的对象属性,这些属性隐藏/隐藏具有相同名称的parentScope对象属性。

more property hiding

显示:

  • 如果我们读取 childScope 。propertyX,并且childScope有 propertyX,那么Prototype链就不会被参考。
  • 如果我们设置 childScope 。PropertyX,则不会参考Prototype链。

最后一个场景:


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

首先删除childScope属性,然后当我们再次尝试访问属性时,会参考Prototype链。

after removing a child property


Angular 作用域继承

竞争者:

  • 以下创建新作用域,并继承 prototypically: ng-repeat,ng-include,ng-switch,ng-controller,scope: true 随以,指令带有 transclude: true
  • 以下创建了一个不继承prototypically的新作用域: 带指令的指令 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生成一个新的子作用域,prototypically从父作用域继承。

ng-include child scopes

在第一个输入文本框中键入( 比如说"77") 会导致子作用域获得一个新的myPrimitive 作用域属性,该属性隐藏/隐藏同名的父作用域属性。 这可能不是你想要/期望的。

ng-include with a primitive

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

ng-include with an object

如果不希望将模型从原语更改为对象,我们可以重写第一个模板以使用 $parent,:


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

在这里输入文本框中键入( 比如说"22") 不会产生新的子属性。 模型现在绑定到父作用域( 因为 $parent 是引用父作用域的子作用域属性)的属性。

ng-include with $parent

对于所有作用域( Prototype或非 Prototype ),Angular 总是通过作用域属性 $parent, $$childHead 和 $$childTail. 跟踪parent-child关系( 例如,一个层次结构) 我通常不在图表中显示这些作用域属性。

对于不涉及表单元素的场景,另一个解决方案是在父作用域上定义一个函数来修改原始作用域。 然后确保子对象总是调用这里函数,因为Prototype继承对子作用域可用。 例如,


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

这是小提琴 样本,使用"父函数"方法。 ( 是这个答案的一部分: http://stackoverflow.com 14104318/215945 。)

参见 http://stackoverflow.com 13782671/215945https://github.com/angular/angular.js/issues/1267.

ng-switch

ng-switch作用域继承工作就像 ng-include 。 因此,如果需要 2 -way数据绑定到父作用域中的一个基元,请使用 $parent, 或者将模型更改为一个对象,然后绑定到该对象的属性。 这将避免对子作用域属性的子作用域隐藏/隐藏。

另请参阅 AngularJS,switch-case的绑定范围

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创建一个新的范围,prototypically继承自父范围,但也分配项目新属性的值在新的子范围。 (新属性的名称是循环的名字。)这里的变量是什么 Angular 源代码ng-repeat实际上是:


childScope = scope.$new();//child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value;//creates a new childScope property

如果项是基元( 在myArrayOfPrimitives中),则基本上将该值的副本分配给新的子作用域属性。 更改属性( 例如,使用 ng-model,因此子作用域 num )的子作用域值不等于更改父作用域引用的数组。 在上面的第一个ng-repeat,每个孩子得到一个范围 num 财产独立于myArrayOfPrimitives数组:

ng-repeat with primitives

这里ng-repeat将无法工作( 就像你想要的或者期望它) 。 在文本框中键入更改灰色框中的值,这些值只在子作用域中可见。 我们需要的是影响myArrayOfPrimitives数组的输入,而不是子作用域基元属性。 为此,我们需要将模型更改为一个对象数组。

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

ng-repeat with objects

( 我只给一行灰色着色,这样它就可以清晰地显示了。)

这将按预期方式工作。在文本框中输入的值更改了灰色框的值,这对子作用域和父作用域都可见。

另请参阅 Difficulty ng-model,ng-repeat和http://stackoverflow.com 13782671/215945

ng-controller

使用ng-controller的嵌套控制器会导致典型的Prototype继承,就像ng-include和ng-switch一样,因此同样的技术应用。 但是,"这被认为是两个控制器通过 $scope 继承共享信息的糟糕形式"-- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 服务应该用来在控制器之间共享数据。

( 如果你真的想通过控制器的作用域继承来共享数据,你就没有什么需要做的了) 。 子作用域可以访问所有父作用域属性。 请参见控制器加载顺序在加载或者导航 。) 时有所不同

指令

  1. 默认( scope: false ) - 指令不创建新作用域,因此这里没有继承。 这很简单,但也很危险,因为 比如 可能认为它在作用域上创建一个新的属性,实际上它在破坏一个现有属性。 这对于编写用作可以重用组件的指令不是一个好的选择。
  2. scope: true - 指令创建一个新的子作用域,prototypically从父作用域继承。 如果多个指令( 在同一个DOM元素上) 请求一个新作用域,则只创建一个新子作用域。 由于我们有"普通"Prototype继承,这就像ng-include和ng-switch一样,所以要小心 2 -way数据绑定到父作用域原语,和子作用域属性的子作用域隐藏/隐藏。
  3. scope: {.. . } - 指令创建一个新的隔离/独立作用域。 它不是prototypically继承的。 这通常是创建可重用组件时的最佳选择,因为该指令不能意外地读取或者修改父作用域。 但是,此类指令通常需要访问一些父作用域属性。 对象哈希用于设置双向绑定( 使用'=') 或者单向绑定( 使用'@') 在父作用域和隔离作用域之间。 还有'&'可以绑定到父作用域表达式。 因此,这些都创建从父作用域派生的局部作用域属性。 注意,属性用于帮助设置绑定 --,你不能只在对象散列中引用父作用域属性名,你必须使用属性。 比如,如果你想绑定到独立作用域中的父属性 parentProp,这将不起作用: <div my-directive>scope: { localProp: '@parentProp' } 必须使用一个属性来指定指令要绑定到的每个父属性: <div my-directive the-Parent-Prop=parentProp>scope: { localProp: '@theParentProp' }
    隔离作用域引用对象的__proto__ 。 隔离 $parent 作用域引用父作用域,因此虽然它是独立的,但不从父作用域继承 prototypically,它仍然是子作用域。
    对于下面的图片
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    另外,假定指令在它的链接函数中执行这里操作: scope.someIsolateProp ="I'm isolated"
    isolated scope
    有关隔离作用域的详细信息,请参阅 http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true - 指令创建一个新的"transcluded"子作用域,prototypically从父作用域继承。 transcluded和独立作用域( 如果有的话) 是兄弟--每个作用域的$parent 属性引用相同的父作用域。 当transcluded和隔离范围都存在时,隔离作用域属性 $$nextSibling 将引用transcluded作用域。 我不知道transcluded作用域有什么细微差别。
    对于下面的图片,使用下面的命令来假设相同的指令: transclude: true
    transcluded scope

这个 fiddle 具有一个 showScope() 函数,可以用来检查一个独立的和transcluded的作用域。 查看小提琴中的注释中的说明。


摘要

有四种类型的作用域:

  1. 常规Prototype作用域继承 -- ng-include,ng-switch,ng-controller,scope: true 随以
  2. 带有复制/赋值 -- ng-repeat的常规Prototype范围继承。 ng-repeat的每次迭代都创建一个新的子作用域,并且新的子作用域总是获得一个新的属性。
  3. 将作用域--指令与 scope: {...} 隔离。 这个不是 Prototype,但是'=','@'和'&'提供了一个通过属性访问父作用域属性的机制。
  4. transcluded作用域--指令随以 transclude: true 。 这也是典型的Prototype范围继承,但它也是任何隔离作用域的兄弟。

对于所有作用域( Prototype或非 Prototype ),Angular 总是通过属性 $parent 和 $$childHead 和 $$childTail. 跟踪parent-child关系( 例如,一个层次结构)

图是用 "* 。dot"文件生成的,这些文件位于 github的。 Tim"Caswell 学习JavaScript的对象图"是对图表使用GraphViz的灵感。

我不想与mark的答案竞争,只是想强调一下,最后让一切都变成了 Javascript继承和它的Prototype链

只有属性读取的属性读取Prototype链,而不是写入。 ,当你设置时


myObject.prop = '123';

它不向上查找链,但当你设置


myObject.myThing.prop = '123';

内部有一个微妙的读,写操作之前,试图查找myThing写提案。 这就是为什么从孩子那里写入 object.properties的原因得到了父对象的对象。

是,子作用域从父作用域继承变量,

下面是一个关于 AngularJS作用域层次结构的简单示例

我想添加一个Prototype继承的示例,使用javascript到 @Scott Driscoll答案。 我们将使用经典继承模式,Object.create() 是 EcmaScript 5规范的一部分。

首先我们创建"父级"对象函数


function Parent(){

}

然后将Prototype添加到"父级"对象函数


 Parent.prototype = {
 primitive : 1,
 object : {
 one : 1
 }
}

创建"子级"对象函数

 
function Child(){

}

 

分配子 Prototype ( 使子Prototype从父Prototype继承)


Child.prototype = Object.create(Parent.prototype);

指定正确的"子级"Prototype构造函数


Child.prototype.constructor = Child;

将方法"changeprops"添加到子 Prototype,它将重写子对象中的"基元"属性值,并在子对象和父对象中更改" object.one"值


Child.prototype.changeProps = function(){
 this.primitive = 2;
 this.object.one = 2;
};

初始化父( dad ) 和子( 子) 对象。


var dad = new Parent();
var son = new Child();

调用子( 子) changeProps方法

 
son.changeProps();

 

检查结果。 。

父基元属性未更改


console.log(dad.primitive);/* 1 */

子基元属性已经更改( 重写)


console.log(son.primitive);/* 2 */

父级和子 object.one 属性已经更改


console.log(dad.object.one);/* 2 */
console.log(son.object.one);/* 2 */

这里的工作示例 http://jsbin.com/xexurukiso/1/edit/

关于 Object.create的更多信息 https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

...