angular的设计者提供了一种很独特的编码方式,让DOM变得更友好了。以前的DOM(至少我觉得)就是一堆堆起来的元素,光看DOM看不出来这页面到底要干嘛(超神的程序员除外),angular的DOM像是一篇能直接阅读的文章,他把方法直接写在了DOM里面,如果英语好的话(当然代码的编辑者也得有合适的命名规范),一个不懂前端的人估计都能直接读懂这个页面要实现的功能。 我认为提供这种能力最直观的就是controller(虽然真正干活的是以 compile为首的一班 provider,不过他们一般在幕后),而controller在不同使用场景下,也会有不一样的表现。 这次我就分享一些我在controller学习使用中碰到的一些有意思的事情。
比如下面这段代码:通过改变输入框内的值改变页面上显示的名字,和统计次数的文字。 当然这个功能不用controller也可以实现,不过居然是说明controller的用途,就这么写了,controller在这里有两个方法: init:初始化数据 saying:把输入值整理成一串字符串
HTML 绑定
<div ng-init="init()" ng-controller="hello"> <p>Name : <input type="text" ng-model="name"></p> <h1>Hello <span ng-bind="name | uppercase"></span></h1> <input type="text" ng-model="number"> <h2 ng-bind='saying()'></h2> </div>JS定义
var app=angular.module('tutorial',[]); app.controller('hello',function($scope){ $scope.init=function(){ $scope.name='Wang'; $scope.number=1; } $scope.saying=function(){ var time=parseInt($scope.number)+1; return 'Mr.'+$scope.name+',this is your '+time+' time to here'; } });在html中绑定controller的逻辑基本就是这样: DOM 通过ng-controller指定能操作自己的controller, controller通过$scope将controller中定义的各种方法映射到DOM元素上(当然还得借助一大堆的ng兄弟去实现具体的工作)。
Created with Raphaël 2.1.0 DOM DOM JS JS ng-controller:init(),saying() $scope对象的方法属性: $scope:name,number,init,saying看着貌似挺简单,而且也很符合angular增强HTML功能的设计初衷。
不过下面我们来考虑一个有意思的问题 在同一个controller绑定在两个DOM元素上的时候,会发生什么?
<div ng-init="init()" ng-controller="hello"> <p>Name : <input type="text" ng-model="name"></p> <h1>Hello <span ng-bind="name | uppercase"></span></h1> <input type="text" ng-model="number"> <h2 ng-bind='saying()'></h2> </div> //和上面一模一样的重复 <div ng-init="init()" ng-controller="hello"> <p>Name : <input type="text" ng-model="name"></p> <h1>Hello <span ng-bind="name | uppercase"></span></h1> <input type="text" ng-model="number"> <h2 ng-bind='saying()'></h2> </div>虽然在现实情况下不太可能发生这种复制一遍的情况,但如果存在一个功能超全的controller,包含了很多能复用的方法(如果真有这种controller,你可以考虑把那些方法扔进service里了),那么他们的属性、数据会共享吗?比如在这种情况下,修改其中任意一个输入框的值,会有几个地方同步改变呢?
只会有一个改变,对于一个controller而言,不管他在多少个DOM上被绑定, 他的作用域(生命周期、或者别的什么,叫什么都行)只在这一块内,两块controller之间没有联系(就算都是修改 scope,其实这俩 scope只是看着一样),改变其中的一个对另一个不会有影响。
改变其中的一个对另一个不会有影响,这话是不是听着特别耳熟(好像在面向对象里面见过)。没错,虽然你看着只是写了一遍controller,但其实angular在使用的时候,还帮你new了一把,所以他们是彼此独立的两个对象。
如果你把代码改成这样,你能看到控制台输出两遍start。
app.controller('hello',function($scope){ //加个log console.log('controller start'); $scope.init=function(){ $scope.name='Wang'; $scope.number=1; } $scope.saying=function(){ var time=parseInt($scope.number)+1; return 'Mr.'+$scope.name+',this is your '+time+' time to here'; } }除了直接绑在DOM上,controller还能直接绑在Directive上。 比如下面这串代码:
HTML部分
<foo-directive foo='foo'></foo-directive>JS部分
app.controller('hello2',function($scope,$http){ console.log('controller2 start'); }); app.directive("fooDirective", ['$http',function($http) { return { controller:'hello2', template : "<h1>自定义指令!</h1>" }; }]);页面上的效果就是打出一段自定义指定的文字。 hello2这个controller在这里可以说完全没有任何功能,可是已经足够说明问题,除了ng-controller以为,angular还能通过directive的controller属性去绑定controller,这个controller不用显式地写在页面上(看着好像有点违背angular的设计初衷,其实人家一开始的设计就是Directive为主,controller为辅的,只是我们一般都从controller开始学而已)。 同样,在这个Directive出现在页面上的时候,这个controller也会被new一份。 这种绑定在Directive上的controller还有一个特色,如果你把这个Directive和之前绑在DOM上的controller写在一个页面上,绑定DOM的controller和绑定Directive的controller谁会先被new?无论这两个DOM元素的先后顺序是什么样的,Directive里的controller都会先跑。 这其实涉及到angular的一个核心部件$compile,以后有机会再细说,这里就先知道Directive里的controller有执行的优先权就行(虽然貌似没啥大用,不过一些异步方案和未来的多线程可以优先放在Directive里)。
除了写在DOM里的ng-controller和写在Directive里的controller,还有一个常见的会出现controller的地方:Directive里的link。
//官方的link代码,5个默认参数 function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }link其实是Directive被$compile编译后执行的一个回调方法,一般用在对DOM元素进行改变的工作上。 个人觉得可以把link直接理解为jquery(当然只是功能上),选中一段DOM结构后对其进行操作。 一般来说link都被用来注册事件,关联数据什么的,不过这次我们关心的是link的第四个默认参数controller,这个controller和之前绑定在DOM或者Directive上的controller有什么关系?
一般来想,这个controller应该拥有绑定的controller(这里是hello2)所有的属性方法吧?
先做一个小实验: 试着打印看看controller究竟有什么。
app.directive("fooDirective", ['$http',function($http) { return { controller:'hello2', link:function(scope, $element, attrs, controllers) { console.log(controllers); }, template : "<h1>自定义指令2!</h1>" }; }]);打印出来一个空对象。。。hello2虽然没有什么功能可也不至于是个空对象吧,看来和我们理解的不太一样,这个controller不是hello2?那他是谁,来做什么? 在官方的解释中,这个controller貌似是个很强大的存在:
controller - the directive’s required controller instance(s) - Instances are shared among all directives, which allows the directives to use the controllers as a communication channel. The exact value depends on the directive’s require property 这么强大的说辞是个空是不是有点说不过去?等会儿,我们好像漏看了一些关键词:instance,controller as a communication channel。 angular的意思难道是要把controller的实例对象本身作为一个传递中介? 既然是实例对象,我把hello2稍微改动了一下:
app.controller('hello2',function($scope,$http,wiki){ console.log('controller2 start'); $scope.hello=function(a){ var addd=a; console.log(addd); } return { hello3:function(a){ alert(a); } } });然后再跑一遍代码: 控制台打出来hello3 function,好激动啊,原来实例指的是这个。那么在实际使用中,我们就可以用controller通过$scope取得不同DOM的值,然后返回给Directive,这是不是就是所谓的communication channel?
居然是communication channel,而且官方的解释还是controllers,那是不是意味着可以传多个controller进去,实现不同controller直接的交互? 果断试试这个,require注入另一个controller hello(require要注入多个controller第一位必须得是自身)
app.directive("fooDirective", ['$http',function($http) { return { controller:'hello2', require:['fooDirective','hello'], link:function(scope, $element, attrs, controllers) { console.log(controllers); }, template : "<h1>自定义指令2!</h1>" }; }]);果断报错,controller不能解析。
其实当然不能解析,hello这个controller还没有实例化怎么可能解析,而且官方也说这是用在Directive直接的controller信息传递,也就是说是用在Directive中实例化的controller上。 那么是不是可以改成这样?
//一个新的directive <runoob-directive aaa='aaa'> <foo-directive foo='foo'></foo-directive> </runoob-directive> app.controller('hello',function($scope){ console.log('controller3 start'); return { hello1:function(a){ alert(a); } } }); app.directive("runoobDirective", ['$http',function($http) { return { controller:'hello', link:function(scope, $element, attrs, controllers) { console.log(controllers); } }; }]); app.directive("fooDirective", ['$http',function($http) { return { controller:'hello2', require:['fooDirective','^runoobDirective'], link:function(scope, $element, attrs, controllers) { console.log(controllers); }, template : "<h1>自定义指令2!</h1>" }; }]);新建了一个Directive,然后很开心的看到控制台打出来一个数组,两个实例化的function,分别来自hello和hello2.而且如果这时候你试试查看scope的话,你会很神奇的发现他们的$id是一样的,也就是说这么写能让两个Directive共享一次compile,感觉这在需要大量数据绑定的地方是一个可以很节约性能的功能。
另外,在Directive中注入多个controller还能让他们彼此继承对方的方法属性(就行call和apply),配合watch用在多个controller通信中感觉很有前途。