所谓继承,比较熟悉,这里就是指定义一个新的controller/service(不同名),继承原来的controller/service,然后在其基础上重写一些功能。
所谓扩展,这里说的是在不产生新的controller/service的情况下,添加或修改原controller/service的功能。
目前研究的结果就是service可以轻松的实现继承和扩展,而controller貌似只能继承。
说到controller,我们在[前面的文章](/2016/08/22/angularJs/用$scope还是用controller as/)中介绍过有两种写法:使用$scope或使用controller as。针对这两种方式的区别,我们也可以使用两种不同的继承方式:
controller as的情况下,特点是controller不再依赖$scope,就跟普通的函数差不多,这个时候可以使用Javascript原生的继承方式。$scope时,可以使用AngularJS内置的$controllerservice,通过依赖注入的方式实现继承。还是直接上例子吧。
使用原生的继承
See the Pen zBQAbw by 曹强 (@ronghuaxueleng) on CodePen.
这个例子有左右两块区域,左边的是parent,右边的是child,它们分别有5行数据需要显示:
sex。用于展示继承时不更改的属性。name。用于展示继承后覆盖的属性。num,左边是孩子的个数,右边是兄弟的个数,显然右边比左边少1。用于显示后面add()方法的结果。add(),左边是parent想多要一个孩子,右边是想child想多要一个兄弟,结果是一样的。用于展示继承时不改变的方法。test(),用于展示继承时覆盖的方法。(不要在意这个随意的名字,因为我已经场景匮乏了。。。)通过这5行,这个例子就基本涵盖了继承时发生的大部分情况。那么切换到JS里看看实现吧。JS里的代码结构大致分为5部分,用注释/* Section 1 */来区分(由于CodePen这工具不支持显示行号,所以只能用代码里的注释来分块讲解了)。
extend,里面的原理不再详述,有兴趣可以看以前写的《《JavaScript高级程序设计》阅读笔记:面向对象之继》。这段代码怎么生成?访问CoffeeScript官网,点击“TRY COFFEESCRIPT”,会打开一个编辑器窗口,左边写CoffeeScript,右边就会生成相应的Javascript。在左边键入
class A extends B,右面就会有extend函数啦。机!智!
FamilyService的AngularJS的service。两个controller都需要依赖它。它的功能很简单,parent和child需要的孩子数量和兄弟数量都由这个service来提供,分别是方法getChildrenCount()和getSiblingCount()。除此之外,每次生孩子的时候它会trigger一个new-child事件,并将孩子数量通过参数传播出去。ParentCtrl这个父controller了,由于采用controller as写法,这里的定义跟普通的对象没区别,在构造函数里定义属性,方法则定义在prototype上。这里值得注意的有两点:add()函数其实仅仅是调用FamilyService的newChild()方法。num属性的改变是通过响应new-child事件来实现的。ChildCtrl定义了,同样,属性的定义在构造函数中,方法定义在prototype上。可以发现,发生继承需要以下几步:__super__来实现对父controller的引用的,因为在extend函数中我们已将讲ParentCtrl的prototype赋值给__super__变量了。这步可以保证把ParentCtrl里定义的属性以及事件响应继承过来。name和num属性,并且更改了事件响应函数的内容,因为new-child事件返回的参数是孩子的总数,这里要减去自己才能得到兄弟的个数。extend函数。extend函数的前面,则extend函数会将ChildCtrl重新定义的add方法用ParentCtrl的覆盖。使用$controllerservice
See the Pen RRmAmq by 曹强 (@ronghuaxueleng) on CodePen.
例子的实际效果与上面的一致,我们直接看JS部分的实现吧。这个代码分为4个部分:
ParentCtrl的定义,这里与前面的不同一个是全部使用$scope,另一个比较特殊的是add方法,它直接调用了一个绑定在this上面的add方法,这样做没什么特殊含义,仅为了演示后面的继承。ChildCtrl的定义,这里的继承是通过var parentCtrl = $controller('ParentCtrl', {$scope: $scope});来实现的,通过依赖注入得到父controller的实例,并将自己的$scope传入,这样,父controller绑定在$scope上面的东西就全部继承到子controller上面了。除此之外,还可以使用变量parentCtrl来引用父controller,跟前面的__super__一样。add方法的覆盖给出了使用parentCtrl的例子。ChildCtrl依赖了$controllerservice。比较
可以发现,其实这两种方式最大的区别就是,原生继承中需要调用extend函数来继承,并且子controller里需要显式调用父controller的构造函数来是实现属性的继承。而使用$controllerservice则只需要依赖注入和传入$scope即可。
service的继承就比较简单了,AngularJS中的service可以认为是new了service构造函数的实例。看下面这个例子,与上面的例子类似,同样是展示了继承过程中不改变或覆盖父service的属性和方法。
See the Pen YWbwoO by 曹强 (@ronghuaxueleng) on CodePen.
熟悉的左右布局,三个属性两个方法。直接看JS中的实现,4个部分:
这里的extend和上面自己写的extend有什么区别呢?其实
angular.extend的功能是一个Shallow Copy,类似上面我们自己的extend中的这段
1 | for (var key in parent) { |
TestCtrl1和TestCtrl2的定义都是TestCtrl,只是一个依赖ParentService,另一个依赖ChildService罢了。其实扩展说白了,就是可以把一个已经定义好的service进行修改,为了保证这个修改的优先级,可以在module的config阶段来实现。废话不说,直接上例子吧。
See the Pen qNGbZx by 曹强 (@ronghuaxueleng) on CodePen.
这个例子与前面的类似,只是页面只有一个部分,实现的结果跟上面的右半部分一致。直接看JS中的实现,除了第3部分其他都是一样的。第3部分中定义了一个extendServcie函数,这个函数是在app的config阶段调用的(见第4部分)。这个函数中依赖了一个特殊的service$provide,扩展功能就由它的decorator方法来实现,方法的第一个参数就是要扩展的service的名称,第二个参数就是实际的扩展。在第二个参数中依赖一个$delegateservice,这个service代表的其实就是TestService本身,可以看到在函数中我们直接使用$delegate去引用原有servcie,并进行随意更改,最终将这个$delegate返回即可。
也许很多人会觉得这种场景很诡异,平常根本不可能用得到。但是我在项目中就曾经遇到一个例子:在单页面应用中我们并没有把所有JS文件压缩在一起,而是根据不同的页面去lazy load不同的文件。具体去load哪些文件定义在一个servcie中,但这些文件名在development和production阶段是不一样的,这样就需要两套不同的文件名配置。利用servcie的扩展,可以在production环境时多include一个JS文件,在这个文件中对servcie进行扩展,更新那些相应的文件名。