Angular Scope

摘要


在AngularJS中, ChildScope通常以原型链的方式继承自ParentScope. 但有一个特殊的情况, 当你在指令中指定了scope:{}, 这会创建一个IsolateScope, 它不从DOM元素所在的Scope上继承属性, 这对于需要创建一个可复用的指令很有帮助.

Scope的继承通常很简单, 以至于大多数情况下你不会意识到它的存在, 但当你尝试在ChildScope里双向数据绑定(例如ng-model)一个ParentScope上的原生变量的时候(例如number, string, boolean), 它会在ChildScope上创建一个同名的属性, 并使你无法再访问到ParentScope上的该属性, 这并不是Angular施了什么魔法, 这是由Javascrip原型继承决定的. 刚入门的AngularJS 开发者通常不会意识到ng-repeat, ng-switch, ng-view, ng-include, ng-if这些指令都会创建一个新的ChildScope, 所以当牵扯到这些指令的时候, 经常会出现一些奇怪的问题. 看看这个例子.

一个最佳实践可以轻松的避免上面提到的原生变量的问题, 就是永远在ng-model中加上一个".", 这样可以确保原型链继承正常工作. 所以

//use

//rather than
.

如果你确实需要绑定原生变量, 有两个解决办法:

  1. 在ChildScope里绑定$parent.parentScopeProperty, 这样ChildScope不会自己创建一个同名的变量.
  2. 在ParentScope里定义一个函数, 在ChildScope里调用, 把原生变量作为参数传入

JavaScript 原型继承


充分理解JavaScript原型继承很重要, 尤其如果你是后端开发人员, 更熟悉类(Class)继承. 下面我们回顾一下原型继承.

假设ParentScope有如下属性, aString, aNumber, anArray, anObject, aFunction. ChildScope继承自它.

Image-1.png

当我们试图从ChildScope中去访问一个ParentScope上的属性, JavaScript会从ChildScope开始一级一级沿着原型链往上找, 直到找到该属性为止. 所以下面这些表达式都为TRUE.

ChildScope.aString === 'parent string'
ChildScope.anArray[1] === 20
ChildScope.anObject.property1 === 'parent prop1'
ChildScope.aFunction() === 'parent output'

假设接下类我们执行如下语句

ChildScope.aString = 'child string'

这句代码会在ChildScope创建一个叫aString的属性, 而ParentScope上的aString不会受到任何影响. 明白这点对接下来讨论ng-repeatng-include很重要.

Image-2.png

接下来我们执行

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

JavaScript会先在ChildScope上寻找anArrayanObject属性, 找寻无果后, 会继续往原型链上一级也就是ParentScope寻找, 所以ParentScope上的这两个属性就被修改了.

Image-3.png

继续执行

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

仔细比较和上一步的区别, 这次是直接对anArrayanObject赋值, 会在ChildScope新增两个对象属性.

Image-3.png

这个jsfiddle 是对上述例子的演示.

Angular Scope 继承


ng-include
假设有如下代码

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

//HTML

每个ng-include都会创建一个ChildScope, 如下图关系:

在第一个输入框中输入"77", 会得到图4结果, 因为这相当于执行了

ChildScope.myPrimitive = 77
Image-4.png

在第二个输入框中输入"99", 会得到图5结果, 因为这相当于执行了

ChildScope.myObject.aNumber = 99
Image-5.png

参考上面说道的JavaScript原型继承, 相信这不难理解.

如摘要中提到的, 如果你确实需要用原生变量, 你可以修改tp11.html


输入"22", 得到图6结果, 因为我们直接引用$parent(对ParentScope的引用)上的属性. 所以不会在ChildScope上创建属性.

Image-6.png

或者你可以使用绑定函数的形式, 参考jsfiddle
ng-switch
ng-include类似, 不再赘述.
ng-repeat
假设有如下代码

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

//HTML

对每一次循环, 每一个ng-repeat会创建一个继承当前Scope的ChildScope. 然后在ChildScope上创建一个属性并赋值(属性的名字就是循环的变量名). 上述代码的运行结果请看图7和8.

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

Image-8.png

他们的区别在于数组中每一项的值是原生类型还是对象. 如果是原生类型, 则会复制一份然后赋值给ChildScope上的新属性(num), 如果是对象, 则把对象的引用赋值给ChildScope上的新属性(obj).
ng-view
TBD, but I think it acts just like ng-include.
ng-controller
互相嵌套的Controller, 就像ng-include, ng-switch一样, 会产生继承关机的Scope. 但是通过$scope在Controller之间共享数据是不好的做法, 应该用Service替代.
directive

  • scope: false default
    不会创建ChildScope, 所以也存在什么继承, 很简单但也很危险, 指令可以直接操作Scope
  • scope: true
    创建一个ChildScope继承自ParentScope, 如果一个DOM元素上同时有多个指令需要新的Scope, 只有一个会被创建.
  • scope: {}
    创建一个IsolateScope, 如果需要访问ParentScope上的属性, 可以使用@, =, <, &, 详情参考Directives
  • transclude: true
    创建一个TranscludeChildScope继承自指令外部的ParentScope(不是指令的Scope), Angular1.3版本以前, TranscludeChildScope的$parent属性指向的是ParentScope. Angular1.3版本以后, TranscludeChildScope的$parent属性指向的是指令的Scope. jsfiddle

https://github.com/angular/angular.js/wiki/Understanding-Scopes

你可能感兴趣的:(Angular Scope)