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
<input type="text" ng-model="someObj.prop1">
//rather than
<input type="text" ng-model="prop1">.

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

  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
<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都会创建一个ChildScope, 如下图关系:

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

ChildScope.myPrimitive = 77
Image-4.png

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

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

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

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

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

输入"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
<ul>
    <li ng-repeat="num in myArrayOfPrimitives">
       <input ng-model="num"></input>
    </li>
</ul>
<ul>
    <li ng-repeat="obj in myArrayOfObjects">
       <input ng-model="obj.num"></input>
    </li>
</ul>

对每一次循环, 每一个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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容