AngularJS 作用域Scope的继承

转载自:http://www.angularjs.cn/A09C
  • 以下方式会创建新的子作用域,并且进行原型继承: ng-repeatng-includeng-switchng-viewng-controller, 用scope: truetransclude: true创建directive。
  • 以下方式会创建新的独立作用域,不会进行原型继承:用scope: { ... }创建directive。这样创建的作用域被称为"Isolate"作用域。

注意:默认情况下创建directive使用了scope: false,不会创建子作用域。

进行原型继承即意味着父作用域在子作用域的原型链上,这是JavaScript的特性。AngularJS的作用域还存在如下内部定义的关系:

  • scope.$parent指向scope的父作用域;
  • scope.$$childHead指向scope的第一个子作用域;
  • scope.$$childTail指向scope的最后一个子作用域;
  • scope.$$nextSibling指向scope的下一个相邻作用域;
  • scope.$$prevSibling指向scope的上一个相邻作用域;

这些关系用于AngularJS内部历遍,如$broadcast和$emit事件广播,$digest处理等。

ng-include

In controller:

$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

In 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

在第一个input框输入"77"将会导致子作用域中新建一个同名属性,其值为77,这不是你想要的结果。

ng-include primitive

在第二个input框输入"99"会直接修改父作用域的myObject对象,这就是JavaScript原型继承机制的作用。

ng-include object

(注:上图存在错误,红色99因为是50,11应该是99)

如果我们不想把model由原始类型改成引用类型——对象,我们也可以使用$parent直接操作父作用域:

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

输入"22"我们得到了想要的结果。

ng-include $parent

另一种方法就是使用函数,在父作用域定义函数,子作用域通过原型继承可运行该函数:

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

请参考:

sample fiddle that uses this "parent function" approach. (This was part of aStack Overflow post.)

http://stackoverflow.com/a/13782671/215945

https://github.com/angular/angular.js/issues/1267.

ng-switch

ng-switchng-include一样。

参考: AngularJS, bind scope of a switch-case?

ng-view

ng-viewng-include一样。

ng-repeat

Ng-repeat也创建子作用域,但有些不同。

In controller:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]

In 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对每一个迭代项Item都会创建子作用域, 子作用域也从父作用域进行原型继承。 但它还是会在子作用域中新建同名属性,把Item赋值给对应的子作用域的同名属性。 下面是AngularJS中ng-repeat的部分源代码:

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

如果Item是原始类型(如myArrayOfPrimitives的11、22), 那么子作用域中有一个新属性(如num),它是Item的副本(11、22). 修改子作用域num的值将不会改变父作用域myArrayOfPrimitives,所以在上一个ng-repeat,每一个子作用域都有一个num 属性,该属性与myArrayOfPrimitives无关联:

ng-repeat primitive

显然这不会是你想要的结果。我们需要的是在子作用域中修改了值后反映到myArrayOfPrimitives数组。我们需要使用引用类型的Item,如上面第二个ng-repeat所示。

myArrayOfObjects的每一项Item都是一个对象——引用类型,ng-repeat对每一个Item创建子作用域,并在子作用域新建obj属性,obj属性就是该Item的一个引用,而不是副本。

ng-repeat object

我们修改子作用域的obj.num就是修改了myArrayOfObjects。这才是我们想要的结果。

参考:

Difficulty with ng-model, ng-repeat, and inputs

ng-repeat and databinding

ng-controller

使用ng-controllerng-include一样也是创建子作用域,会从父级controller创建的作用域进行原型继承。但是,利用原型继承来使父子controller共享数据是一个糟糕的办法。 "it is considered bad form for two controllers to share information via $scope inheritance",controllers之间应该使用 service进行数据共享。

(如果一定要利用原型继承来进行父子controllers之间数据共享,也可以直接使用。 请参考: Controller load order differs when loading or navigating)

directives

  1. 默认 (scope: false) - directive使用原有作用域,所以也不存在原型继承,这种方式很简单,但也很容易出问题——除非该directive与html不存在数据绑定,否则一般情况建议使用第2条方式。
  2. scope: true - directive创建一个子作用域, 并且会从父作用域进行原型继承。 如果同一个DOM element存在多个directives要求创建子作用域,那么只有一个子作用域被创建,directives共用该子作用域。
  3. scope: { ... } - directive创建一个独立的“Isolate”作用域,没有原型继承。这是创建可复用directive组件的最佳选择。因为它不会直接访问/修改父作用域的属性,不会产生意外的副作用。这种directive与父作用域进行数据通信有如下四种方式(更详细的内容请参考Developer Guide):

    1. = or =attr “Isolate”作用域的属性与父作用域的属性进行双向绑定,任何一方的修改均影响到对方,这是最常用的方式;
    2. @ or @attr “Isolate”作用域的属性与父作用域的属性进行单向绑定,即“Isolate”作用域只能读取父作用域的值,并且该值永远的String类型
    3. & or &attr “Isolate”作用域把父作用域的属性包装成一个函数,从而以函数的方式读写父作用域的属性,包装方法是$parse,详情请见API-$parse

    “Isolate”作用域的__proto__是一个标准Scope object (the picture below needs to be updated to show an orange 'Scope' object instead of an 'Object'). “Isolate”作用域的$parent同样指向父作用域。它虽然没有原型继承,但它仍然是一个子作用域。

    如下directive:

     <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> 

    scope:

     scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }

    link函数中:

     scope.someIsolateProp = "I'm isolated"

    isolate scope

    请注意,我们在link函数中使用attrs.$observe('interpolated', function(value) { ... }来监测@属性的变化。

    更多请参考: http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/

  4. transclude: true - directive新建一个“transcluded”子作用域,并且会从父作用域进行原型继承。需要注意的是,“transcluded”作用域与“Isolate”作用域是相邻的关系(如果“Isolate”作用域存在的话) -- 他们的$parent属性指向同一个父作用域。“Isolate”作用域的$$nextSibling指向“transcluded”作用域。

    更多请参考: AngularJS two way binding not working in directive with transcluded scope

    transcluded scope

    demo: fiddle

总结

AngularJS存在四种作用域:

  1. 普通的带原型继承的作用域 -- ng-includeng-switchng-controller, directive with scope: true
  2. 普通的带原型继承的,并且有赋值行为的作用域 -- ng-repeatng-repeat为每一个迭代项创建一个普通的有原型继承的子作用域,但同时在子作用域中创建新属性存储迭代项;
  3. “Isolate”作用域 -- directive with scope: {...}, 该作用域没有原型继承,但可以通过'=', '@', 和 '&'与父作用域通信。
  4. “transcluded”作用域 -- directive with transclude: true,它也是普通的带原型继承的作用域,但它与“Isolate”作用域是相邻的好基友。

Diagrams were generated with GraphViz "*.dot" files, which are on github. Tim Caswell's "Learning JavaScript with Object Graphs" was the inspiration for using GraphViz for the diagrams.

The above was originally posted on StackOverflow.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值