深入理解Angular.JS中的Scope继承


Posted in Javascript onJune 04, 2017

前言

AngularJS中scope之间的继承关系使用JavaScript的原型继承方式实现。本文结合AngularJS Scope的实现以及相关资料谈谈原型继承机制。下面来看看详细的介绍:

基本原理

在JavaScript中,每创建一个构造函数(constructor),就会同时给该函数生成一个指向原型对象的属性prototype。每个原型对象又获得一个constructor属性指向相应的构造函数,原型对象的其他属性和方法从Object继承而来。每个通过构造函数创建的实例,都包含一个指向构造函数原型对象的内部属性[[Prototype]](在浏览器中通常实现为__proto__)。构造函数、原型对象和实例三者的关系如下 (图片来源:《JavaScript高级程序设计(第3版)》):

深入理解Angular.JS中的Scope继承

person1和person2为构造函数Person创建的两个实例,可以通过[[Prototype]]属性访问原型对象Person Prototype,获得原型中定义的所有方法和属性。Person构造函数的prototype属性同样指向Person Prototype原型对象。以上这些概念是理解原型继承的基础,下面我们来看原型链的概念。如果把一个类型的实例赋值给一个原型对象会发生什么?根据上图中的关系,此时的原型对象包含指向另一个原型的属性,而另一个原型中也包含着指向另一个构造函数的属性。

效果如下图:

深入理解Angular.JS中的Scope继承

SuperType为一个父类型,在原型中定义了属性property和方法getSuperValue;SubType是一个子类型,定义了属性subproperty和方法getSubValue。instance为SubType的一个实例。这里通过下面的关键代码,将SubType的原型对象变为SuperType对象的实例:

SubType.prototype = new SuperType(); 
SubType.prototype.getSubValue = function(){ 
 return this.subproperty; 
};

我们看到,SubType的原型对象中有来自SuperType实例对象的property属性,以及自己在原型上定义的getSubValue方法,通过[[Prototype]]属性,又可以进一步访问SuperType原型对象中的成员。假如SuperType的原型也被赋值成某个类型的实例,依次类推,那么可以通过[[Prototype]]属性一直向上回溯,形成一条直通Object原型对象的原型链。上面的例子只展示了链条的前两环。

通过原型链的实现,SubType的实例继承了SuperType实例的的所有实例成员和原型成员。例如,若要访问instance.getSuperValue,首先在instance实例内部搜索,没有该方法;然后通过原型链向上回溯,找到SubType原型对象,也没有该方法;再通过[[Prototype]]属性继续回溯,来到SuperType的原型对象,找到该方法。

以上描述的这种继承方式就是原型继承。在ES5以后,可以使用Object提供的create方法规范化上述过程,详细请参考这里。AngularJS的Scope继承关系的实现类似上述过程。

Scope继承实现

在Angular中,想要定义一个Scope的child Scope可以通过scope.$new方法实现,而$new方法本身的实现就体现了上述原型继承的思想。首先,$new方法接受两个参数:isolated和parent。第一个参数表示创建的child scope是否是一个隔离的(isolated)。隔离的scope不继承parent scope的原型,只是在层次结构(hierachy)上属于其child scope,这种结构是Digest过程的基础。isolated scope的一个好处是避免parent scope的成员被更改,在directive的实现里很有用。第二个参数指定创建的child scope的parent scope,如果不指定,默认为当前调用$new方法的scope。Angular中$new的实现类似:

$new : function(isolate, parent) { 
  var child; 
  parent = parent || this; 
  if (isolate) { 
   child = new Scope(); 
   child.$root = this.$root; 
  } else { 
   if (!this.$$ChildScope) { 
   this.$$ChildScope = createChildScopeClass(this); 
   } 
   child = new this.$$ChildScope(); 
  } 
  child.$parent = parent; 
  //... 
  return child; 
},//...

可以看出,如果是isolate为true,则使用Scope类型构造函数创建一个child对象。如果isolate为false或者未指定,则创建一个child scope原型继承于当前scope,这个过程由createChildScopeClass提供的构造函数实现:

function createChildScopeClass(parent) { 
 function ChildScope() { 
  this.$$watchers = null; 
  this.$$listeners = {};//... 
 } 
 ChildScope.prototype = parent; 
 return ChildScope; 
 }

这里定义了ChildScope类型,包括其需要的属性。然后将该类型的prototye属性设置为传入的scope实例(即前面的this),这就是前面阐述的原型继承。之后通过ChildScope创建的scope对象都是原型继承于parent的,即可以访问parent scope的所有成员。结合$new的代码,如果child非隔离,则child可以访问当前scope对象中的所有成员(例如$digest,$apply等方法以及自定义成员)。这就解释了在我们自己创建的controller对应的scope里,可以访问$rootScope提供的成员,因为我们的scope最终原型继承自root scope,因而可以通过原型链向上回溯到root scope的实例。

在前面一篇文章中,谈到了Angular中Digest过程。当调用scope.$apply方法时,实际上是从root scope开始,按照scope的层次结构,调用每个scope的$digest方法。这就是为什么在Scope的构造函数中会设置$root属性:

function Scope() { 
  this.$parent = null;//... 
  this.$root = this; 
  this.$$destroyed = false; 
  this.$$listeners = {}; 
  //... 
}

对于一般child scope,$root会通过原型继承得到,在root scope构造以后,后续的scope都可以访问$root对象,即是root scope对象。对于isolated scope,由于是通过Scope构造函数创建(非原型继承),$root被child scope覆盖,需要将$root属性设置为parent的$root属性,如前面$new的实现。这就保证了在任何一个scope中始终能拿到root scope的实例,也就可以完成自上而下的Digest过程,在$apply等方法的实现中,使用$rootScope代替$root,二者相同:

$apply: function(expr) { 
  beginPhase('$apply'); 
  try { 
   return this.$eval(expr); 
  } finally { 
   clearPhase(); 
  } 
  finally { 
   $rootScope.$digest(); 
  } 
},//...

$rootScope是$RootScopeProvider提供的Scope类型实例,是最先初始化的scope对象。在开发中,我们可以这样使用child scope:

.controller('smallCatCtrl', [ 
 '$scope', function($scope){ 
    
  var child = $scope.$new(); 
  child.text = 'cat'; 
   
  var child1 = $scope.$new(true); 
  child1.value = 0; 
   
   var child2 = $scope.$new(true, child); 
   child2.value = 1; 
   
  child2.$watch('value', function(oldValue, newValue){ 
   console.log('child2.value changed'); 
  }); 
  child1.$watch('value', function(oldValue, newValue){ 
   console.log('child1.value changed'); 
  }) 
  child.$watch('text', function(oldValue, newValue){ 
   console.log('child.text changed'); 
  }); 
  console.log(child2.text); 
 
 }]);

在这段代码中,首先创建$scope的一个子scope----child,没有给$new指定参数,意味着child原型继承于$scope。同时定义了child的属性text。接下来创建$scope的第二个子scope----child1,传入$new的参数要求child1是isolated scope,并且在层次结构上是$scope的后代。同时定义了其value属性。最后创建scope child2,它也是一个isolated scope,不同的是它以child为层次结构上的parent scope。

这段代码的输出如下:

深入理解Angular.JS中的Scope继承

首先,child2只是在层次结构上继承于child,因此没有把child实例作为原型,也就没有text属性,第一行输出undefined。
由于Digest过程按scope层次结构自上而下进行,类似于树的深度遍历过程。在该例中scope的顺序为$scope->child->child2->child1,因此三个watch listener函数的输出也按照上面的顺序。

本文参考资料:

1. AngularJS 官方文档

2. AngularJS 源代码

3. JavaScript 高级程序设计(第3版)

4. Build Your Own AngularJS

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
js模拟点击事件实现代码
Nov 06 Javascript
javacript获取当前屏幕大小
Jun 04 Javascript
ionic js 模型 $ionicModal 可以遮住用户主界面的内容框
Jun 06 Javascript
js实现界面向原生界面发消息并跳转功能
Nov 22 Javascript
jQuery设置和获取select、checkbox、radio的选中值方法
Jan 01 Javascript
JS实现非首屏图片延迟加载的示例
Jan 06 Javascript
基于node搭建服务器,写接口,调接口,跨域的实例
May 13 Javascript
Vue-cli3项目配置Vue.config.js实战记录
Jul 29 Javascript
JS数组去重的6种方法完整实例
Dec 08 Javascript
详解微信小程序开发用户授权登陆
Apr 24 Javascript
微信小程序 冒泡事件原理解析
Sep 27 Javascript
Vue-Cli项目优化操作的实现
Oct 27 Javascript
yarn的使用与升级Node.js的方法详解
Jun 04 #Javascript
npm国内镜像 安装失败的几种解决方案
Jun 04 #Javascript
Angular 4依赖注入学习教程之InjectToken的使用(八)
Jun 04 #Javascript
Angular 4依赖注入学习教程之ValueProvider的使用(七)
Jun 04 #Javascript
Angular 4依赖注入学习教程之Injectable装饰器(六)
Jun 04 #Javascript
Angular 4依赖注入学习教程之FactoryProvider配置依赖对象(五)
Jun 04 #Javascript
JavaScript基础之this详解
Jun 04 #Javascript
You might like
PHP生成静态页面详解
2006/12/05 PHP
Javascript中valueOf与toString区别浅析
2013/03/19 Javascript
jQuery 取值、赋值的基本方法整理
2014/03/31 Javascript
Node.js编程中客户端Session的使用详解
2015/06/23 Javascript
js生成随机数的方法实例
2015/10/16 Javascript
BootStrap文件上传样式超好看【持续更新】
2016/05/10 Javascript
HTML中使背景图片自适应浏览器大小实例详解
2017/04/06 Javascript
bootstrap响应式表格实例详解
2017/05/15 Javascript
JavaScript实现精美个性导航栏筋斗云效果
2017/10/29 Javascript
chorme 浏览器记住密码后input黄色背景处理方法(两种)
2017/11/22 Javascript
React 无状态组件(Stateless Component) 与高阶组件
2018/08/14 Javascript
微信小程序缓存过期时间的使用详情
2019/05/12 Javascript
javascript实现函数柯里化与反柯里化过程解析
2019/10/08 Javascript
vue 实现element-ui中的加载中状态
2020/11/11 Javascript
django实现前后台交互实例
2017/08/07 Python
python虚拟环境virtualenv的安装与使用
2017/09/21 Python
python 将字符串完成特定的向右移动方法
2019/06/11 Python
python tkinter组件摆放方式详解
2019/09/16 Python
Python基础之字符串常见操作经典实例详解
2020/02/26 Python
Python 找出英文单词列表(list)中最长单词链
2020/12/14 Python
基于CSS3的animation属性实现微信拍一拍动画效果
2020/06/22 HTML / CSS
HTML5声音录制/播放功能的实现代码
2018/05/03 HTML / CSS
英国标志性奢侈品牌:Burberry
2016/07/28 全球购物
Styleonme中文网:韩国高档人气品牌
2017/06/21 全球购物
欧洲最大的拼图游戏商店:JigsawPuzzle.co.uk
2018/07/04 全球购物
铭宣海淘转运:美国、日本、英国转运等全球转运公司
2019/09/10 全球购物
.NET里面什么时候需要调用垃圾回收
2015/06/01 面试题
测控技术与通信工程毕业生自荐信范文
2013/12/28 职场文书
公司承诺书格式
2014/05/21 职场文书
党员评议表自我评价范文
2014/10/20 职场文书
2015年度酒店客房部工作总结
2015/05/25 职场文书
运动会跳远广播稿
2015/08/19 职场文书
八年级历史教学反思
2016/02/19 职场文书
某药房的新员工入职告知书!
2019/07/15 职场文书
MySQL数据库事务的四大特性
2022/04/20 MySQL
mysql sock 文件解析及作用讲解
2022/07/15 MySQL