当前位置:首页 > 日记 > 正文

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

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

前言

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

基本原理

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

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

效果如下图:

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。

这段代码的输出如下:

首先,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

总结

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

相关文章

关于jQuery中fade | ,show | 起始

关于jQuery中fade | ,show | 起始

发现,位置,电脑软件,jQuery,fade,最近在鼓弄主页的时候想要添加一个音乐播放的插件,暂时使用网易与音乐外链播放器,效果是在右下角弹出和消失,于是问题来了:show()和fade()函数是用来显示或者隐藏元素的函数,可以为其传入时间参数,使得函数在多少…

ps怎么快速删除隐藏图层和空白图层

ps怎么快速删除隐藏图层和空白图层

图层,删除,空白,快速,电脑软件,ps文件中有隐藏图层和空白图层,这些隐藏的图层会增加PSD文档的大小增加缓存占用,反正那些图层暂时也用不到,还是删除比较好,信我们就来看看详细的教程。软件名称:Adobe Photoshop 8.0 中文完整绿色破解版软件大小:1…

three.js加载obj模型的实例代码

three.js加载obj模型的实例代码

模型,实例代码,加载,电脑软件,js,three.js是一款webGL框架,由于其易用性被广泛应用。如果你要学习webGL,抛弃那些复杂的原生接口从这款框架入手是一个不错的选择。好了,下面通过一段代码给大家介绍three.js加载obj模型,具体代码如下所示:<!DOCTY…

关于AngularJs数据的本地存储详解

关于AngularJs数据的本地存储详解

数据,本地存储,详解,电脑软件,AngularJs,第一、创建一个factory来储存和调取你的数据(你可以单独创建一个js文件,按照语义命名如:dataService.js。然后在你的主页面引入这个JS文件)<!--引入到你的主页面里面--><script src="dataService.js…

ES6中Class类的静态方法实例小结

ES6中Class类的静态方法实例小结

静态方法,实例,电脑软件,Class,本文实例讲述了ES6中Class类的静态方法。分享给大家供大家参考,具体如下:以前看过的es6的东西,又忘了,再总结下:类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示…

教你如何巧妙设定匿名FTP的安全

教你如何巧妙设定匿名FTP的安全

教你如何,巧妙,电脑软件,FTP,在网络上,匿名FTP是一个很常用的服务,常用于软件下载网站,软件交流网站等,为了提高匿名FTP服务开放的过程中的安全性,我们就这一问题进行一些讨论。  以下的设定方式是由过去许多网站累积的经验与建议组成。我们认…

vue之nextTick全面解析

vue之nextTick全面解析

电脑软件,vue,nextTick,简介vue是非常流行的框架,他结合了angular和react的优点,从而形成了一个轻量级的易上手的具有双向数据绑定特性的mvvm框架。本人比较喜欢用之。在我们用vue时,我们经常用到一个方法是this.$nextTick,相信你也用过。我常…

WEB服务器系统盘权限简单设置

WEB服务器系统盘权限简单设置

系统盘,服务器,权限,设置,简单,其实网上已经很多这样的文章了,但是我遇到的情况用网上的方法不好用,这几天弄我那服务器弄的脑袋都大了,总出问题 昨天ASP又连接不到MDB了,在网上找了好多资料 问了好多人,最开始时候先是把ASP程序问题排除…

PHP判断FORM表单或URL参数来的数据

PHP判断FORM表单或URL参数来的数据

表单,数据,参数,方法,整数,PHP判断FORM表单或URL参数来的数据是否为整数,is_int函数对于FORM表单或URL参数过来的数据是没有办法判断是否是整数的,因为FORM过来的是字符串。用is_numeric可以判断是否为数字类型,再判断是否有小数点就可以判断…

QQ语音怎么转换成文字qq语音转化为

QQ语音怎么转换成文字qq语音转化为

文字,语音,方法,转换成,转化为,  当你在电脑或手机端登录QQ后,不方便接听语音消息时该怎么办呢?大家一定会想着如何将QQ语音转换成文字消息吧?虽说可以转换,但前提是对方给你发送的是普通话语音消息哦!如果说的家乡话,QQ系统可没那么智能翻…

WPS文字怎么实现文章中局部式的分

WPS文字怎么实现文章中局部式的分

文字,局部,文章,电脑软件,WPS,  很多人都看见过文章分栏的效果,给大家推荐的是一种&ldquo;局部式&rdquo;的分栏,何为&ldquo;局部式&rdquo;,就是将我们整片文章中的一部分文字来进行分栏。以下是小编为您带来的关于WPS文字实现文章中局部式的…

Windows Server 2003服务器重启IIS

Windows Server 2003服务器重启IIS

故障,服务器,步骤,重启,方法,在Windows Server 2003服务器中,很多IIS故障可以通过重新启动的方法加以解决。经过重新启动IIS服务,很多问题(甚至表面看起来比较严重的问题)一般都可以排除。这是因为重新启动IIS服务可以强迫系统重置IIS进程的内…