Mar*_*cok 1018 javascript inheritance prototype prototypal-inheritance angularjs
该API参考范围页面说:
范围可以从父范围继承.
该开发者指南范围页说:
范围(原型)从其父范围继承属性.
那么,子范围是否始终从其父范围继承原型?有例外吗?当它继承时,它是否总是正常的JavaScript原型继承?
Mar*_*cok 1736
快速回答:
子范围通常原型继承自其父范围,但并非总是如此.此规则的一个例外是指令scope: { ... }- 这会创建一个"原型"继承的"隔离"范围.在构建"可重用组件"指令时经常使用此构造.
至于细微差别,范围继承通常是直接的......直到您需要在子范围内进行双向数据绑定(即表单元素,ng-模型).如果您尝试从子范围内部绑定到父范围中的基元(例如,数字,字符串,布尔值),则Ng-repeat,ng-switch和ng-include会使您失意.它不像大多数人期望的那样工作.子作用域获取其自己的属性,该属性隐藏/隐藏同名的父属性.你的解决方法是
新AngularJS开发商往往没有意识到ng-repeat,ng-switch,ng-view,ng-include和ng-if所有新创建子作用域,因此问题常常显示出来时,这些指令都参与其中.(有关问题的快速说明,请参阅此示例.)
通过遵循始终具有'.'的"最佳实践",可以轻松避免与原语的这个问题.在您的ng模型中 - 观看3分钟.Misko演示了原始绑定问题ng-switch.
有一个 '.' 在你的模型中将确保原型继承发挥作用.所以,使用
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
Run Code Online (Sandbox Code Playgroud)
也放在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'
Run Code Online (Sandbox Code Playgroud)
假设我们这样做:
childScope.aString = 'child string'
Run Code Online (Sandbox Code Playgroud)
不参考原型链,并且向childScope添加了新的aString属性. 此新属性使用相同的名称隐藏/隐藏parentScope属性. 当我们在下面讨论ng-repeat和ng-include时,这将变得非常重要.

假设我们这样做:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
Run Code Online (Sandbox Code Playgroud)
查询原型链是因为在childScope中找不到对象(anArray和anObject).可以在parentScope中找到对象,并在原始对象上更新属性值.没有新属性添加到childScope; 没有创建新对象.(注意,在JavaScript数组和函数中也是对象.)

假设我们这样做:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
Run Code Online (Sandbox Code Playgroud)
未查询原型链,子范围获取两个新对象属性,这些属性隐藏/隐藏具有相同名称的parentScope对象属性.

小贴士:
最后一个场景:
delete childScope.anArray
childScope.anArray[1] === 22 // true
Run Code Online (Sandbox Code Playgroud)
我们首先删除了childScope属性,然后当我们再次尝试访问该属性时,查阅原型链.

竞争者:
scope: true,directive with transclude: true.scope: { ... }.这样就形成了"隔离"范围.请注意,默认情况下,指令不会创建新范围 - 即默认值为scope: false.
假设我们在控制器中:
$scope.myPrimitive = 50;
$scope.myObject = {aNumber: 11};
Run Code Online (Sandbox Code Playgroud)
在我们的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>
Run Code Online (Sandbox Code Playgroud)
每个ng-include都会生成一个新的子作用域,它原型继承自父作用域.

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

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

如果我们不想将模型从基元更改为对象,我们可以重写第一个模板以使用$ parent:
<input ng-model="$parent.myPrimitive">
Run Code Online (Sandbox Code Playgroud)
在此输入文本框中键入(例如,"22")不会导致新的子属性.该模型现在绑定到父作用域的属性(因为$ parent是引用父作用域的子作用域属性).

对于所有范围(原型或非原型),Angular始终通过范围属性$ parent,$$ childHead和$$ childTail跟踪父子关系(即层次结构).我通常不会在图表中显示这些范围属性.
对于不涉及表单元素的场景,另一种解决方案是在父作用域上定义一个函数来修改基元.然后确保子节点始终调用此函数,该函数由于原型继承而可用于子作用域.例如,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
Run Code Online (Sandbox Code Playgroud)
这是一个使用这种"父函数"方法的小提琴.(这个小提琴是作为这个答案的一部分写的:https://stackoverflow.com/a/14104318/215945.)
另见/sf/answers/964787001/和https://github.com/angular/angular.js/issues/1267.
ng-switch范围继承就像ng-include一样工作.因此,如果需要双向数据绑定到父作用域中的基元,请使用$ parent,或将模型更改为对象,然后绑定到该对象的属性.这将避免子范围隐藏/遮蔽父范围属性.
另请参阅AngularJS,绑定switch-case的范围?
Ng-repeat的工作方式略有不同.假设我们在控制器中:
$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects = [{num: 101}, {num: 202}]
Run Code Online (Sandbox Code Playgroud)
在我们的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>
Run Code Online (Sandbox Code Playgroud)
对于每个项目/迭代,ng-repeat创建一个新范围,该范围原型继承自父范围,但它还将项目的值分配给新子范围上的新属性.(新属性的名称是循环变量的名称.)以下是ng-repeat的Angular源代码实际上是:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
Run Code Online (Sandbox Code Playgroud)
如果item是基元(如在myArrayOfPrimitives中),则基本上将值的副本分配给新的子范围属性.更改子作用域属性的值(即使用NG-模型,因此子范围num)并不会改变阵列父范围引用.因此,在上面的第一个ng-repeat中,每个子作用域都获得一个num独立于myArrayOfPrimitives数组的属性:

这种ng-repeat不起作用(就像你想要/期望的那样).键入文本框会更改灰色框中的值,这些值仅在子作用域中可见.我们想要的是输入影响myArrayOfPrimitives数组,而不是子范围原始属性.为此,我们需要将模型更改为对象数组.
因此,如果item是一个对象,则会将对原始对象(而不是副本)的引用分配给新的子范围属性.更改子范围属性的值(即,使用ng-model,因此obj.num)会更改父范围引用的对象.所以在上面的第二个ng-repeat中,我们有:

(我将一条线涂成灰色,以便清楚它的位置.)
这按预期工作.键入文本框会更改灰色框中的值,这些值对子作用域和父作用域都可见.
另请参阅ng-model,ng-repeat和输入的难度,以及 /sf/answers/964787001/
使用ng-controller嵌套控制器会产生正常的原型继承,就像ng-include和ng-switch一样,因此适用相同的技术.但是,"两个控制器通过$ scope继承共享信息被认为是不好的形式" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 应该使用服务在两者之间共享数据而控制器.
(如果您确实希望通过控制器范围继承来共享数据,则无需执行任何操作.子范围将可以访问所有父范围属性.另请参阅控制器加载顺序在加载或导航时不同)
scope: false) - 该指令不创建新范围,因此这里没有继承.这很容易,但也很危险,因为例如,指令可能会认为它正在范围内创建新属性,而实际上它正在破坏现有属性.对于编写旨在作为可重用组件的指令,这不是一个好的选择.scope: true - 该指令创建一个新的子作用域,它原型继承自父作用域.如果多个指令(在同一DOM元素上)请求新范围,则只创建一个新的子范围.由于我们具有"正常"原型继承,这类似于ng-include和ng-switch,因此要警惕双向数据绑定到父作用域基元,以及子作用域隐藏/遮蔽父作用域属性.scope: { ... } - 该指令创建一个新的隔离/隔离范围.它没有原型继承.在创建可重用组件时,这通常是您的最佳选择,因为该指令不会意外地读取或修改父作用域.但是,此类指令通常需要访问一些父作用域属性.对象散列用于在父作用域和隔离作用域之间设置双向绑定(使用'=')或单向绑定(使用'@').还有'&'绑定到父范围表达式.因此,这些都创建了从父作用域派生的本地作用域属性.请注意,属性用于帮助设置绑定 - 您不能仅在对象哈希中引用父范围属性名称,您必须使用属性.例如,如果要绑定到parentProp隔离范围中的父属性,则不起作用:<div my-directive>和scope: { localProp: '@parentProp' }.必须使用属性来指定指令要绑定到的每个父属性:<div my-directive the-Parent-Prop=parentProp>和scope: { localProp: '@theParentProp' }.
__proto__引用对象.隔离范围的$ parent引用父范围,因此虽然它是隔离的并且不从父范围继承原型,但它仍然是子范围.
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">,
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
scope.someIsolateProp = "I'm isolated"
transclude: true - 该指令创建一个新的"transcluded"子作用域,它原型继承自父作用域.transcluded和隔离范围(如果有)是兄弟 - 每个范围的$ parent属性引用相同的父范围.当transcluded和isolate范围都存在时,隔离范围属性$$ nextSibling将引用转换范围.我不知道转换范围有任何细微差别.
transclude: true

这个小提琴有一个showScope()功能,可用于检查隔离和转换范围.请参阅小提琴中的注释中的说明.
范围有四种类型:
scope: truescope: {...}.这个不是原型,但是'=','@'和'&'提供了一种通过属性访问父作用域属性的机制.transclude: true.这个也是正常的原型范围继承,但它也是任何隔离范围的兄弟.对于所有范围(原型或非原型),Angular始终通过属性$ parent和$$ childHead以及$$ childTail跟踪父子关系(即层次结构).
使用graphviz "*.dot"文件生成图表,这些文件位于github上.Tim Caswell的" 使用对象图学习JavaScript "是将GraphViz用于图表的灵感.
Sco*_*oll 140
我绝不想与Mark的答案竞争,但只是想强调最终使所有内容点击为Javascript继承及其原型链的新手.
只有属性读取搜索原型链,而不是写入.所以当你设置
myObject.prop = '123';
Run Code Online (Sandbox Code Playgroud)
它不会查找链条,但是当你设置时
myObject.myThing.prop = '123';
Run Code Online (Sandbox Code Playgroud)
在写入操作中有一个微妙的读取,试图在写入其prop之前查找myThing.这就是为什么从子节点写入object.properties会获取父节点的对象.
tyl*_*lik 21
我想用javascript向@Scott Driscoll回答添加一个原型继承的例子.我们将使用经典的继承模式与Object.create(),这是EcmaScript 5规范的一部分.
首先我们创建"父"对象函数
function Parent(){
}
Run Code Online (Sandbox Code Playgroud)
然后将原型添加到"父"对象功能
Parent.prototype = {
primitive : 1,
object : {
one : 1
}
}
Run Code Online (Sandbox Code Playgroud)
创建"Child"对象功能
function Child(){
}
Run Code Online (Sandbox Code Playgroud)
分配子原型(让子原型从父原型继承)
Child.prototype = Object.create(Parent.prototype);
Run Code Online (Sandbox Code Playgroud)
分配适当的"Child"原型构造函数
Child.prototype.constructor = Child;
Run Code Online (Sandbox Code Playgroud)
将方法"changeProps"添加到子原型,它将重写Child对象中的"原始"属性值,并在Child和Parent对象中更改"object.one"值
Child.prototype.changeProps = function(){
this.primitive = 2;
this.object.one = 2;
};
Run Code Online (Sandbox Code Playgroud)
启动父(父)和子(子)对象.
var dad = new Parent();
var son = new Child();
Run Code Online (Sandbox Code Playgroud)
调用Child(son)changeProps方法
son.changeProps();
Run Code Online (Sandbox Code Playgroud)
检查结果.
父原始属性没有改变
console.log(dad.primitive); /* 1 */
Run Code Online (Sandbox Code Playgroud)
子原始属性发生了变化(重写)
console.log(son.primitive); /* 2 */
Run Code Online (Sandbox Code Playgroud)
父和子object.one属性已更改
console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */
Run Code Online (Sandbox Code Playgroud)
这里的工作示例http://jsbin.com/xexurukiso/1/edit/
有关Object.create的更多信息,请访问https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
| 归档时间: |
|
| 查看次数: |
148560 次 |
| 最近记录: |