文章转载英文:what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

中文:http://www.lovelucy.info/understanding-scopes-in-angularjs.html

一、AngularJS 中的 Scope(scope即作用域、作用范围)所涉及知识点大致预览图

二、AngularJS中控制器间嵌套问题

在使用 AngularJS 嵌套 Controller 的时候。因为每个 Controller 都有它对应的 Scope(相当于作用域、控制范围),所以 Controller 的嵌套,也就意味着 Scope 的嵌套。

这个时候如果两个 Scope 内都有同名的 Model 会发生什么呢?从子 Scope 怎样更新父 Scope 里的 Model 呢?

2.1 父子嵌套控制器scope中属性名相同时(准确说基本数据类型属性名)情况

在AngularJS中子scope会继承父scope中的对象,但当你试下对基本数据类型(string, number, boolean)的 双向数据绑定 时,子 Scope 的属性隐藏(覆盖)了父 Scope 中的同名属性,对子 Scope 属性(表单元素)的更改并不更新父 Scope 属性的值。

在线编辑代码  请点击我哦 这种行为实际上不是 AngularJS 特有的,JavaScript 本身的原型链就是这样工作的。

开发者通常都没有意识到 ng-repeat, ng-switch, ng-view 和 ng-include 统统都创建了他们新的子 scopes,所以在用到这些 directive 时也经常出问题。

2.1 子 Scope 怎样更新父 Scope 里的 Model 呢?

解决方案一、不使用基本数据类型,而在 Model 里永远多加一个点 <input type="text" ng-modal="someObj.greeting"/> 变成对象形式 在线编辑代码请点击

解决方案二、在子 Scope 中使用 $parent.greeting 这将阻止子 Scope 创建它自己的属性。 在线编辑代码请点击

三、JavaScript原型继承

假设父类 parentScope 有如下成员属性 aString, aNumber, anArray, anObject, 以及 aFunction。子类 childScope 原型继承父类 parentScope,于是我们有

如果子 Scope 尝试去访问 parentScope 中定义的属性,JavaScript 会先在子 Scope 中查找,如果没有该属性,则找它继承的 scope 去获取属性,如果继承的原型对象 parentScope 中都没有该属性,那么继续在它的原型中寻找,从原型链一直往上直到到达 rootScope。所以,下面的表达式结果都是 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 中的同名属性。在下面我们讨论 ng-repeat 和 ng-include 时这个概念很重要。

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

原型链被查询了,因为对象 anArray 和 anObject 在 childScope 中没有找到。它们在 parentScope 中被找到了,并且值被更新。childScope 中没有增加新的属性,也没有任何新的对象被创建。(注:在 JavaScript 中,array 和 function 都是对象)

  

假设我们执行这个操作:

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

原型链没有被查询,并且子 Scope 新加入了两个新的对象属性,它们隐藏(覆盖)了 parentScope 中的同名对象属性。

  

应该可以总结

  • 如果读取 childScope.propertyX,并且 childScope 有属性 propertyX,那么原型链没有被查询。
  • 如果设置 childScope.propertyX,原型链不会被查询。

最后一种情况

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

我们从 childScope 删除了属性,则当我们再次访问该属性时,原型链会被查询。删除对象的属性会让来自原型链中的属性浮现出来。

四、AngularJS 的 Scope 继承

  • 创建新的 Scope,并且原型继承:ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true
  • 创建新的 Scope,但不继承:directive with scope: { ... }。它会创建一个独立 Scope。

注:默认情况下 directive 不创建新 Scope,即默认参数是 scope: false

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 会生成一个子 Scope,每个子 Scope 都继承父 Scope。

输入(比如”77″)到第一个 input 文本框,则子 Scope 将获得一个新的 myPrimitive 属性,覆盖掉父 Scope 的同名属性。这可能和你预想的不一样。

输入(比如”99″)到第二个 input 文本框,并不会在子 Scope 创建新的属性,因为 tpl2.html 将 model 绑定到了一个对象属性(an object property),原型继承在这时发挥了作用,ngModel 寻找对象 myObject 并且在它的父 Scope 中找到了。

  

如果我们不想把 model 从 number 基础类型改为对象,我们可以用 $parent 改写第一个模板:

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

输入(比如”22″)到这个文本框也不会创建新属性了。model 被绑定到了父 scope 的属性上(因为 $parent 是子 Scope 指向它的父 Scope 的一个属性)。

对于所有的 scope (原型继承的或者非继承的),Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系(也就是继承关系),图中为简化而未画出这些属性。

在没有表单元素的情况下,另一种方法是在父 Scope 中定义一个函数来修改基本数据类型。因为有原型继承,子 Scope 确保能够调用这个函数。例如,

// 父 Scope 中
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}

查看 DEMO。参考 StackOverflow

ng-switch

ng-switch 的原型继承和 ng-include 一样。所以如果你需要对基本类型数据进行双向绑定,使用 $parent,或者将其改为 object 对象并绑定到对象的属性,防止子 Scope 覆盖父 Scope 的属性。

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

ng-repeat

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

对于每一个 Item,ng-repeat 创建新的 Scope,每一个 Scope 都继承父 Scope,但同时 item 的值也被赋给了新 Scope 的新属性(新属性的名字为循环的变量名)。Angular ng-repeat 的源码实际上是这样的:

childScope = scope.$new(); // 子 scope 原型继承父 scope ...
childScope[valueIdent] = value; // 创建新的 childScope 属性

如果 item 是一个基础数据类型(就像 myArrayOfPrimitives),本质上它的值被复制了一份赋给了新的子 scope 属性。改变这个子 scope 属性值(比如用 ng-model,即 num)不会改变父 scope 引用的 array。所以上面第一个 ng-repeat 里每一个子 scope 获得的 num 属性独立于 myArrayOfPrimitives 数组:

这样的 ng-repeat 和你预想中的不一样。在 Angular 1.0.2 及更早的版本,向文本框中输入会改变灰色格子的值,它们只在子 Scope 中可见。Angular 1.0.3+ 以后,输入文本不会再有任何作用了。(参考 StackOverflow 上的解释)我们希望的是输入能改变 myArrayOfPrimitives 数组,而不是子 Scope 里的属性。为此我们必须将 model 改为一个关于对象的数组(array of objects)。

所以如果 item 是一个对象,则对于原对象的一个引用(而非拷贝)被赋给了新的子 Scope 属性。改变子 Scope 属性的值(使用 ng-model,即 obj.num)也就改变了父 Scope 所引用的对象。所以上面第二个 ng-repeat 可表示为:

ng-controller

使用 ng-controller 进行嵌套,结果和 ng-include 和 ng-switch 一样是正常的原型继承。所以做法也一样不再赘述。然而“两个 controller 使用 $scope 继承来共享信息被认为是不好的做法”(来自 这里),应该使用 service 在 controller 间共享数据。

如果你确实要通过继承来共享数据,那么也没什么特殊要做的,子 Scope 可以直接访问所有父 Scope 的属性。参考 Controller load order differs when loading or navigating

directives 分情况讨论

    1. 默认 scope: false – directive 不会创建新的 Scope,所以没有原型继承。这看上去很简单,但也很危险,因为你会以为 directive 在 Scope 中创建了一个新的属性,而实际上它只是用到了一个已存在的属性。这对编写可复用的模块和组件来说并不好。
    2. scope: true – 这时 directive 会创建一个新的子 scope 并继承父 scope。如果在同一个 DOM 节点上有多个 directive 都要创建新 scope,则只有一个新 Scope 会创建。因为有正常的原型继承,所以和 ng-include, ng-switch 一样要注意基础类型数据的双向绑定,子 Scope 属性会覆盖父 Scope 同名属性。
    3. scope: { ... } – 这时 directive 创建一个独立的 scope,没有原型继承。这在编写可复用的模块和组件时是比较好的选择,因为 directive 不会不小心读写父 scope。然而,有时候这类 directives 又经常需要访问父 scope 的属性。对象散列(object hash)被用来建立这个独立 Scope 与父 Scope 间的双向绑定(使用 ‘=’)或单向绑定(使用 ‘@’)。还有一个 ‘&’ 用来绑定父 Scope 的表达式。这些统统从父 Scope 派生创建出本地的 Scope 属性。注意,HTML 属性被用来建立绑定,你无法在对象散列中引用父 Scope 的属性名,你必须使用一个 HTML 属性。例如,<div my-directive> 和 scope: { localProp: '@parentProp' } 是无法绑定父属性 parentProp 到独立 scope的,你必须这样指定: <div my-directive the-Parent-Prop=parentProp> 以及 scope: { localProp: '@theParentProp' }。独立的 scope 中 __proto__ 引用了一个 Scope 对象(下图中的桔黄色 Object),独立 scope 的 $parent 指向父 scope,所以尽管它是独立的而且没有从父 Scope 原型继承,它仍然是一个子 scope。

      下面的图中,我们有 <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">和 scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
      同时,假设 directive 在它的 link 函数里做了 scope.someIsolateProp = "I'm isolated"

注意:在 link 函数中使用 attrs.$observe('attr_name', function(value) { ... } 来获取独立 Scope 用 ‘@’ 符号替换的属性值。例如,在 link 函数中有 attrs.$observe('interpolated', function(value) { ... } 值将被设为 11. (scope.interpolatedProp 在 link 函数中是 undefined,相反scope.twowayBindingProp 在 link 函数中定义了,因为用了 ‘=’ 符号)
更多参考 http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/

4.transclude: true – 这时 directive 创建了一个新的 “transcluded” 子 scope,同时继承父 scope。所以如果模板片段中的内容(例如那些将要替代 ng-transclude 的内容)要求对父         Scope 的基本类型数据进行双向绑定,使用 $parent,或者将 model 一个对象的属性,防止子 Scope 属性覆盖父 Scope 属性。

transcluded 和独立 scope (如果有)是兄弟关系,每个 Scope 的 $parent 指向同一个父 Scope。当模板中的 scope 和独立 Scope 同时存在,独立 Scope 属性 $$nextSibling 将会指向模板中的 Scope。更多关于 transcluded scope 的信息,参考 AngularJS two way binding not working in directive with transcluded scope

在下图中,假设 directive 和上个图一样,只是多了 transclude: true

查看 在线 DEMO,例子里有一个 showScope() 函数可以用来检查独立 Scope 和它关联的 transcluded scope。

五、归纳总结

一共有四种 Scope:

  1. 普通进行原型继承的 Scope —— ng-include, ng-switch, ng-controller, directive with scope: true
  2. 普通原型继承的 Scope 但拷贝赋值 —— ng-repeat。 每个 ng-repeat 的循环都创建新的子 Scope,并且子 Scope 总是获得新的属性。
  3. 独立的 isolate scope —— directive with scope: {...}。它不是原型继承,但 ‘=’, ‘@’ 和 ‘&’ 提供了访问父 Scope 属性的机制。
  4. transcluded scope —— directive with transclude: true。它也遵循原型继承,但它同时是任何 isolate scope 的兄弟。

对于所有的 Scope,Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系。

转深入理解 AngularJS 的 Scope作用域的更多相关文章

  1. 深入理解AngularJs-scope(二)

    深入理解AngularJs-scope(一)中,我们对AngularJs的脏检测及其触发.异步任务队列进行了学习.紧接上一篇文章 深入理解AngularJs-scope(一),我们来看看scope对以 ...

  2. AngularJs之Scope作用域

    前言: 上篇博文AngularJs之directive中说了Scope作用域是个大坑,所以拿出来作为重点总结! 什么是scope AngularJS 中,作用域是一个指向应用模型的对象,它是表达式的执 ...

  3. 深入理解 AngularJS 的 Scope

    JavaScript 的原型继承就是奇葩. 之前在 V2EX 上看到讨论说,不会 OOP 的 JavaScript 的程序员就是野生程序员.看来我是属于野生的.   一.遇到的问题 问题发生在使用 A ...

  4. 深入理解 AngularJS 的 Scope(转)

    一.遇到的问题 问题发生在使用 AngularJS 嵌套 Controller 的时候.因为每个 Controller 都有它对应的 Scope(相当于作用域.控制范围),所以 Controller ...

  5. 深入理解AngularJs-scope(一)

    进入正文前的说明:本文中的示例代码并非AngularJs源码,而是来自书籍<<Build Your Own AngularJs>>, 这本书的作者仅依赖jquery和lodas ...

  6. 转: 深入理解 AngularJS 的 Scope

      查看 DEMO.参考 StackOverflow. ng-switch ng-switch 的原型继承和 ng-include 一样.所以如果你需要对基本类型数据进行双向绑定,使用 $parent ...

  7. AngularJs $rootScope.Scope 作用域操作

    这里讲的是一些scope的操作,如创建/注销/各种监听及scope间的通信等等. $rootScope.Scope 可以使用$injector通过$rootScope关键字检索的一个根作用域. 可以通 ...

  8. 理解 AngularJS 的 Scope

    一.遇到的问题 问题发生在使用 AngularJS 嵌套 Controller 的时候.因为每个 Controller 都有它对应的 Scope(相当于作用域.控制范围),所以 Controller ...

  9. AngularJS(2)-Scope作用域和控制器

    $scope: 根作用域 所有的应用都有一个 $rootScope,它可以作用在 ng-app 指令包含的所有 HTML 元素中. $rootScope 可作用于整个应用中.是各个 controlle ...

随机推荐

  1. 2016年7月微软MVP申请開始了!

    2016年7月微软MVP申请開始了! CSDN与微软合作,长期为用户提供申请"微软最有价值专家"的平台.希望有兴趣.资历的朋友以及正在朝这个方向努力的朋友能够积极參与. 2016年 ...

  2. 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记38 Unwind Segue反向过渡

    之前我们接触过了segue,这是IOS中最主要的传递方式,今天来解说一下怎样进行反向的segue. 反向过渡的使用前提是发出过渡的MVC必须是由目标MVC直接或者间接正向过渡来的.反向过渡是唯一不会创 ...

  3. hdu5353 Average

    Problem Description There are n soda sitting around a round table. soda are numbered from 1 to n and ...

  4. 巧用CAS解决数据一致性问题

    缘起:在高并发的分布式环境下,对于数据的查询与修改容易引发一致性问题,本文将分享一种非常简单但有效的优化方法. 一.业务场景 业务场景为,购买商品的过程要对余额进行查询与修改,大致的业务流程如下: ( ...

  5. Spring Boot实战之逐行释义HelloWorld

    一.前言  研究Spring boot也有一小段时间了,最近会将研究东西整理一下给大家分享,大概会有10~20篇左右的博客,整个系列会以一个简单的博客系统作为基础,因为光讲理论很多东西不是特别容易理解 ...

  6. Ubuntu 编译安装 OpenCV 3.1

    目标系统:Ubuntu 16.04-64bit OpenCV 版本:opencv-3.1.0 安装步骤 安装 cmake.sudo apt-get isntall cmake cmake-qt-gui ...

  7. 聊聊js中的typeof

    内容: 1.typeof 2.值类型和引用类型 3.强制类型转换 typeof 官方文档:typeof 1.作用: 操作符返回一个字符串,指示未经计算的操作数的类型. 2.语法: typeof ope ...

  8. Python爬虫(二十四)_selenium案例:执行javascript脚本

    本章叫介绍如何使用selenium在浏览器中使用js脚本,更多内容请参考:Python学习指南 隐藏百度图片 #-*- coding:utf-8 -*- #本篇将模拟执行javascript语句 fr ...

  9. spring boot oauth2的一些记录

    oauth2及时从一个项目A申请另一个项目B的访问的时候,不用在项目A输入项目B的用户名和密码,个人理解先跳转到项目B,利用项目B的用户名和密码得到一个code之类的,这里有点像openID,不过不是 ...

  10. 【二十六】php之文件编程

    1.获取文件的相关信息 fopen.fstat.fclose(打开文件.获取文件相关信息.关闭文件) filesize.filectime.filemtime.fileatime(文件大小.上次cha ...