API参考范围页面说:

作用域可以从父作用域继承。

开发者指南范围页面说:

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

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


当前回答

我绝不想与Mark的回答竞争,只是想强调这篇文章,作为Javascript继承及其原型链的新手,它最终让一切都变得轻松。

只有属性读取搜索原型链,而不是写入。所以当你设置

myObject.prop = '123';

它不会看链条,但当你设置

myObject.myThing.prop = '123';

在写操作中有一个微妙的读操作,它试图在写入它的道具之前查找myThing。这就是为什么写反对。子对象的属性获取父对象的对象。

其他回答

快速回答: 子作用域通常从父作用域原型继承,但并非总是如此。该规则的一个例外是具有scope的指令:{…}——这将创建一个“隔离”作用域,该作用域没有原型继承。在创建“可重用组件”指令时,经常使用这个构造。

至于细微差别,作用域继承通常是直截了当的……直到您需要在子作用域中进行双向数据绑定(即表单元素ng-model)。如果您试图从子作用域内绑定到父作用域中的原语(例如,数字、字符串、布尔值),Ng-repeat、ng-switch和ng-include会使您陷入困境。它并没有像大多数人期望的那样工作。子作用域获得自己的属性,该属性隐藏/隐藏同名的父属性。你的变通办法是

在你的模型的父类中定义对象,然后在子类中引用该对象的属性:parentObj.someProp 使用美元的父母。parentScopeProperty(并不总是可能的,但比1简单。在可能的情况下) 在父作用域上定义一个函数,并从子作用域调用它(并不总是可以)

新的AngularJS开发人员通常没有意识到ng-repeat、ng-switch、ng-view、ng-include和ng-if都会创建新的子作用域,所以当涉及到这些指令时,问题就会出现。(请看这个例子来快速说明这个问题。)

通过遵循always have a '的“最佳实践”,原语的这个问题可以很容易地避免。在你的ng-models中,看3分钟。Misko演示了ng-switch的原语绑定问题。

有一个'。’将确保原型继承发挥作用。因此,使用

<input type="text" ng-model="someObj.prop1">

<!--rather than
<input type="text" ng-model="prop1">`
-->

L-o-n-g回答:

JavaScript原型继承

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

首先对原型继承有一个坚实的理解是很重要的,特别是如果您来自服务器端背景,并且您更熟悉类继承。我们先复习一下。

假设parentScope有属性asstring, aNumber, anArray, anObject和function。如果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。如果childScope有propertyX,那么原型链就不会被查询。 如果我们设置childScope。propertyX,原型链不被参考。

最后一个场景:

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

我们首先删除了childScope属性,然后当我们试图再次访问该属性时,会参考原型链。


Angular作用域继承

竞争者:

下面创建新的作用域,并继承原型:ng-repeat, ng-include, ng-switch, ng-controller, scope: true的指令,transclude: true的指令。 下面创建了一个新的作用域,该作用域不继承prototypical:指令的作用域:{…}。这反而创建了一个“隔离”作用域。

注意,默认情况下,指令不会创建新的作用域——即默认为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作用域属性,该属性隐藏/隐藏同名的父作用域属性。这可能不是你想要/期望的。

在第二个输入文本框中输入(例如“99”)不会产生新的子属性。因为tpl .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;
}

下面是一个使用这种“父函数”方法的小提琴示例。(小提琴是这个答案的一部分:https://stackoverflow.com/a/14104318/215945。)

参见https://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),本质上是将值的副本赋给新的子scope属性。改变子作用域属性的值(即使用ng-model,因此是子作用域num)不会改变父作用域引用的数组。所以在上面的第一个ng-repeat中,每个子作用域都有一个独立于myArrayOfPrimitives数组的num属性:

这个ng-repeat将不起作用(就像你想要/期望的那样)。在文本框中输入将更改灰色框中的值,这些值仅在子范围中可见。我们想要的是让输入影响myArrayOfPrimitives数组,而不是子作用域原语属性。为了实现这一点,我们需要将模型更改为对象数组。

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

(我把其中一条线涂成灰色,这样就能清楚地看到它的走向。)

这与预期的一样。在文本框中输入将更改灰色框中的值,这些值对于子域和父域都是可见的。

参见ng-model、ng-repeat和输入的困难 https://stackoverflow.com/a/13782671/215945

ng-controller

使用ng-controller嵌套控制器会导致正常的原型继承,就像ng-include和ng-switch一样,因此可以应用相同的技术。 然而,“两个控制器通过$scope继承共享信息被认为是一种糟糕的形式”——http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 服务应该用于在控制器之间共享数据。

(如果你真的想通过控制器范围继承来共享数据,你不需要做任何事情。子作用域可以访问父作用域的所有属性。 参见控制器加载顺序不同时加载或导航)

指令

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. 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. 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" For more information on isolate scopes see http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/ 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

这个小提琴有一个showScope()函数,可以用来检查一个隔离的和透射的作用域。请参阅小提琴评论中的说明。


总结

范围有四种类型:

normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true 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. isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '@', and '&' provide a mechanism to access parent scope properties, via attributes. 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属性来跟踪父子关系(即一个层次结构)。

图表是用graphviz "*生成的。"文件,在github上。Tim Caswell的“用对象图学习JavaScript”是使用GraphViz制作图表的灵感来源。

我绝不想与Mark的回答竞争,只是想强调这篇文章,作为Javascript继承及其原型链的新手,它最终让一切都变得轻松。

只有属性读取搜索原型链,而不是写入。所以当你设置

myObject.prop = '123';

它不会看链条,但当你设置

myObject.myThing.prop = '123';

在写操作中有一个微妙的读操作,它试图在写入它的道具之前查找myThing。这就是为什么写反对。子对象的属性获取父对象的对象。

我想在@Scott Driscoll的回答中添加一个javascript原型继承的例子。我们将使用Object.create()的经典继承模式,Object.create()是EcmaScript 5规范的一部分。

首先,我们创建“父”对象函数

function Parent(){

}

然后在“Parent”对象函数中添加一个原型

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

创建“Child”对象函数

function Child(){

}

分配子原型(使子原型继承父原型)

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

分配适当的“子”原型构造函数

Child.prototype.constructor = Child;

在子原型中添加方法“changeProps”,该方法将重写子对象中的“primitive”属性值并更改“object”。在子对象和父对象中都有一个"值

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

启动父(dad)和子(son)对象。

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

调用Child (son) changeProps方法

son.changeProps();

检查结果。

父元素属性没有改变

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

子原语属性已更改(已重写)

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

父和子对象。一个属性改变了

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

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

更多关于对象的信息。创建在这里https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create