1 前言
前端技术的发展是如此之快,各种优秀技术、优秀框架的出现简直让人目不暇接,紧跟时代潮流,学习掌握新知识自然是不敢怠慢。
AngularJS是google在维护,其在国外已经十分火热,可是国内的使用情况却有不小的差距,参考文献/网络文章也很匮乏。这里便将我学习AngularJS写成文档,一方面作为自己学习路程上的记录,另一方面也给有兴趣的同学一些参考。
首先我自己也是一名学习者,会以学习者的角度来整理我的行文思路,这里可能只是些探索,有理解或是技术上的错误还请大家指出;其次我特别喜欢编写小例子来把一件事情说明白,故在文中会尽可能多的用示例加代码讲解,我相信这会是一种比较好的方式;最后,我深知AngularJS的使用方式跟jQuery的使用方式有很大不同,在大家都有jquery、ext经验的条件下对于angular的学习会困难重重,不过我更相信在大家的坚持下,能够快速的学好AngularJS,至少咱也能深入了解到AngularJS的基本思想,对咱们以后自己的插件开发、项目开发都会有很大的启示。
2AngularJS概述
2.1AngularJS是什么?@H_301_12@
AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是一个框架,不是类库,是像EXT一样提供一整套方案用于设计web应用。它不仅仅是一个JavaScript框架,因为它的核心其实是对HTML标签的增强。
何为HTML标签增强?其实就是使你能够用标签完成一部分页面逻辑,具体方式就是通过自定义标签、自定义属性等,这些HTML原生没有的标签/属性在ng中有一个名字:指令(directive)。后面会详细介绍。那么,什么又是动态web应用呢?与传统web系统相区别,web应用能为用户提供丰富的操作,能够随用户操作不断更新视图而不进行url跳转。ng官方也声明它更适用于开发CRUD应用,即数据操作比较多的应用,而非是游戏或图像处理类应用。
为了实现这些,ng引入了一些非常棒的特性,包括模板机制、数据绑定、模块、指令、依赖注入、路由。通过数据与模板的绑定,能够让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上。
另外一个疑问,ng是MVC框架吗?还是MVVM框架?官网有提到ng的设计采用了MVC的基本思想,而又不完全是MVC,因为在书写代码时我们确实是在用ng-controller这个指令(起码从名字上看,是MVC吧),但这个controller处理的业务基本上都是与view进行交互,这么看来又很接近MVVM。让我们把目光移到官网那个非醒目的title上:“AngularJS—SuperheroicJavaScriptMVWFramework”。
2.2AngularJS简单介绍@H_301_12@
AngularJS重新定义了前端应用的开发方式。面对HTML和JavaScript之间的界线,它
非但不畏缩不前,反而正面出击,提出了有效的解决方案。
很多前端应用的开发框架,比如Backbone、EmberJS等,都要求开发者继承此框架特有的一些JavaScript对象。这种方式有其长处,但它不必要地污染了开发者自己代码的对象空间,还要求开发者去了解内存里那些抽象对象。尽管如此我们还是接受了这种方式,因为网络最初的设计无法提供我们今天所需的交互性,于是我们需要框架,来帮我们填补JavaScript和HTML之间的鸿沟。而且有了它,你不用再“直接”操控DOM,只要给你的DOM注上Metadata(即AngularJS里的directive们),然后让AngularJS来帮你操纵DOM。同时,AngularJS不依赖(也不妨碍)任何其他的框架。你甚至可以基于其它的框架来开发AngularJS应用。
API地址:@H_301_12@http://docs.angularjs.org/api/;
AngularJS在github上的中文粗译版地址:https://github.com/basestyle/angularjs-cn。
2.3什么时候该用AngularJS@H_301_12@
AngularJS是一个MV*框架,最适于开发客户端的单页面应用。它不是个功能库,而是用来开发动态网页的框架。它专注于扩展HTML的功能,提供动态数据绑定(databinding),而且它能跟其它框架(如jQuery)合作融洽。
如果你要开发的是单页应用,AngularJS就是你的上上之选。Gmail、GoogleDocs、Twitter和Facebook这样的应用,都很能发挥AngularJS的长处。但是像游戏开发之类对DOM进行大量操纵、又或者单纯需要极高运行速度的应用,就不是AngularJS的用武之地了。
3AugularJS特性
AngularJS是一个新出现的强大客户端技术,提供给大家的一种开发强大应用的方式。这种方式利用并且扩展HTML,CSS和javascript,并且弥补了它们的一些非常明显的不足。本应该使用HTML来实现而现在由它开发的动态一些内容。
AngularJS有五个最重要的功能和特性:
3.1特性一:双向的数据绑定
数据绑定可能是AngularJS最酷最实用的特性。它能够帮助你避免书写大量的初始代码从而节约开发时间。一个典型的web应用可能包含了80%的代码用来处理,查询和监听DOM。数据绑定是的代码更少,你可以专注于你的应用。
我们想象一下Model是你的应用中的简单事实。你的Model是你用来读取或者更新的部分。数据绑定指令提供了你的Model投射到view的方法。这些投射可以无缝的,毫不影响的应用到web应用中。
传统来说,当model变化了。开发人员需要手动处理DOM元素并且将属性反映到这些变化中。这个一个双向的过程。一方面,model变化驱动了DOM中元素变化,另一方面,DOM元素的变化也会影响到Model。这个在用户互动中更加复杂,因为开发人员需要处理和解析
这些互动,然后融合到一个model中,并且更新View。这是一个手动的复杂过程,当一个应用非常庞大的时候,将会是一件非常费劲的事情。
这里肯定有更好的解决方案!那就是AngularJS的双向数据绑定,能够同步DOM和Model等等。
这里有一个非常简单的例子,用来演示一个input输入框和<h1>元素的双向绑定(例01):
说明:实际效果请大家看AngularJS/demo/index.html
3.2特性二:模板
在AngularJS中,一个模板就是一个HTML文件。但是HTML的内容扩展了,包含了很多帮助你映射model到view的内容。
HTML模板将会被浏览器解析到DOM中。DOM然后成为AngularJS编译器的输入。AngularJS将会遍历DOM模板来生成一些指导,即,directive(指令)。所有的指令都负责针对view来设置数据绑定。
我们要理解AuguarJS并不把模板当做String来操作。输入AngularJS的是DOM而非string。数据绑定是DOM变化,不是字符串的连接或者innerHTML变化。使用DOM作为输入,而不是字符串,是AngularJS区别于其它的框架的最大原因。使用DOM允许你扩展指令词汇并且可以创建你自己的指令,甚至开发可重用的组件。
最大的好处是为设计师和开发者创建了一个紧密的工作流。设计师可以像往常一样开发标签,然后开发者拿过来添加上功能,通过数据绑定将会使得这个过程非常简单。
这里有一个例子,我们使用ng-repeat指令来循环图片数组并且加入img模板,如下:
functionAlbumCtrl($scope){
scope.images=[
{"image":"img/image_01.png","description":"Image01description"},
{"image":"img/image_02.png","description":"Image02description"},sans-serif; font-size:16px"> {"image":"img/image_03.png","description":"Image03description"},sans-serif; font-size:16px"> {"image":"img/image_04.png","description":"Image04description"},sans-serif; font-size:16px"> {"image":"img/image_05.png","description":"Image05description"}
];
}
<divng-controller="AlbumCtrl">
<ul>
<ling-repeat="imageinimages">
<imgng-src="http://m.cnblogs.com/142260/{{image.thumbnail}}" rel="nofollow"/>
</li>
</ul>
</div>
这里还有一件事值得提一句,AngularJS并不强制你学习一个新的语法或者从你的应用中提出你的模板。
3.3特性三:MVC
针对客户端应用开发AngularJS吸收了传统的MVC基本原则。MVC或者Model-View-Controll设计模式针对不同的人可能意味不同的东西。AngularJS并不执行传统意义上的MVC,更接近于MVVM(Moodel-View-viewmodel)。
Model
model是应用中的简单数据。一般是简单的javascript对象。这里没有必要继承框架的classes,使用proxy对象封装或者使用特别的setter/getter方法来访问。事实上我们处理vanillajavascript的方法就是一个非常好的特性,这种方法使得我们更少使用应用的原型。
viewmodel是一个用来提供特别数据和方法从而维护指定view的对象。
viewmodel是$scope的对象,只存在于AnguarJS的应用中。$scope只是一个简单的js对象,这个对象使用简单的API来侦测和广播状态变化。
Controller
controller负责设置初始状态和参数化$scope方法用以控制行为。需要指出的controller并不保存状态也不和远程服务互动。
View
view是AngularJS解析后渲染和绑定后生成的HTML。这个部分帮助你创建web应用的架构。$scope拥有一个针对数据的参考,controller定义行为,view处理布局和互动。
3.4特性四:服务和依赖注入
AngularJS服务其作用就是对外提供某个特定的功能。
AngularJS拥有内建的依赖注入(DI)子系统,可以帮助开发人员更容易的开发,理解和测试应用。
DI允许你请求你的依赖,而不是自己找寻它们。比如,我们需要一个东西,DI负责找创建并且提供给我们。
为了而得到核心的AngularJS服务,只需要添加一个简单服务作为参数,AngularJS会侦测并且提供给你:
functionEditCtrl($scope,$location,$routeParams){
//Somethingcleverhere...
你也可以定义自己的服务并且让它们注入:
angular.module('MyServiceModule',[]).
factory('notify',['$window',function(win){
returnfunction(msg){
win.alert(msg);
};
}]);
functionmyController(scope,notifyService){
scope.callNotify=function(msg){
notifyService(msg);
myController.$inject=['$scope','notify'];
3.5特性五:指令(Directives)
指令是我个人最喜欢的特性。你是不是也希望浏览器可以做点儿有意思的事情?那么AngularJS可以做到。
指令可以用来创建自定义的标签。它们可以用来装饰元素或者操作DOM属性。可以作为标签、属性、注释和类名使用。
这里是一个例子,它监听一个事件并且针对的更新它的$scope,如下:
myModule.directive('myComponent',function(mySharedService){
return{
restrict:'E',sans-serif; font-size:16px"> controller:function($scope,$attrs,mySharedService){
$scope.$on('handleBroadcast',function(){
$scope.message='Directive:'+mySharedService.message;
});
},sans-serif; font-size:16px"> replace:true,sans-serif; font-size:16px"> template:'<input>'
然后,你可以使用这个自定义的directive来使用:
<my-componentng-model="message"></my-component>
使用一系列的组件来创建你自己的应用将会让你更方便的添加,删除和更新功能。
4功能介绍
4.1数据绑定
AngularJS的双向数据绑定,意味着你可以在Mode(JS)中改变数据,而这些变动立刻就会自动出现在View上,反之亦然。即:一方面可以做到model变化驱动了DOM中元素变化,另一方面也可以做到DOM元素的变化也会影响到Model。
在我们使用jQuery@H_301_12@的时候,代码中会大量充斥类似这样的语句:@H_301_12@var@H_301_12@val=$(‘@H_301_12@#id@H_301_12@’@H_301_12@).val();$(@H_301_12@‘@H_301_12@#id@H_301_12@’@H_301_12@).html(str);@H_301_12@等等,即频繁的@H_301_12@DOM@H_301_12@操作(读取和写入),其实我们的最终目的并不是要操作@H_301_12@DOM@H_301_12@,而是要实现业务逻辑。@H_301_12@ng@H_301_12@的绑定将让你摆脱@H_301_12@DOM@H_301_12@操作,只要模板与数据通过声明进行了绑定,两者将随时保持同步,最新的数据会实时显示在页面中,页面中用户修改的数据也会实时被记录在数据模型中。@H_301_12@
从View到Controller再到View的数据交互(例01@H_301_12@):@H_301_12@
<htmlng-app="demoApp">
……
<inputtype="text"ng-model="user.name"placeholder="请输入名称@H_301_12@"/>@H_301_12@
Hello,{{user.name}}!@H_301_12@
关键:ng-app@H_301_12@、@H_301_12@ng-model@H_301_12@和@H_301_12@{{user.name}}@H_301_12@
首先:<html>@H_301_12@元素的@H_301_12@ng-app@H_301_12@属性。标识这个@H_301_12@DOM@H_301_12@里面的内容将启用@H_301_12@AngularJS应用。
其次:告诉AngularJS@H_301_12@,对页面上的“@H_301_12@user.name@H_301_12@”这个@H_301_12@Model@H_301_12@进行双向数据绑定。@H_301_12@
第三:告诉AngularJS@H_301_12@,在“@H_301_12@{{@H_301_12@user.name}}”这个指令模版上显示“@H_301_12@user.name”这个Model@H_301_12@的数据。@H_301_12@
从Server到Controller再到View的数据交互(例02@H_301_12@):@H_301_12@
<divng-controller="demoController">
<inputtype="text"ng-model="user.name"disabled="disabled"/>
<ahref="javascript:void(0);" target="_blank" rel="nofollow">获取名字</a>@H_301_12@
demoApp.controller("demoController",function($http,$scope){
$scope.getAjaxUser=function(){
// $http.get({url:"../xxx.action"}).success(function(data){
// $scope.user=data;
// });
$scope.user={"name":"从@H_301_12@JOSN@H_301_12@中获取的名称@H_301_12@","age":22};@H_301_12@
改变$scope@H_301_12@中的@H_301_12@user@H_301_12@,@H_301_12@View@H_301_12@也会自动更新。@H_301_12@
4.2scopes、module、controller
4.2.1scopes
$scope是一个把@H_301_12@view@H_301_12@(一个@H_301_12@DOM@H_301_12@元素)连结到@H_301_12@controller@H_301_12@上的对象。在我们的@H_301_12@MVC@H_301_12@结构里,这个@H_301_12@$scope@H_301_12@将成为@H_301_12@model@H_301_12@,它提供一个绑定到@H_301_12@DOM@H_301_12@元素(以及其子元素)上的@H_301_12@excecutioncontext@H_301_12@。@H_301_12@
尽管听起来有点复杂,但$scope@H_301_12@实际上就是一个@H_301_12@JavaScript@H_301_12@对象,@H_301_12@controller@H_301_12@和@H_301_12@view@H_301_12@都可以访问它,所以我们可以利用它在两者间传递信息。在这个@H_301_12@$scope@H_301_12@对象里,我们既存储数据,又存储将要运行在@H_301_12@view@H_301_12@上的函数。@H_301_12@
每一个Angular@H_301_12@应用都会有一个@H_301_12@$rootScope@H_301_12@。这个@H_301_12@$rootScope@H_301_12@是最顶级的@H_301_12@scope@H_301_12@,它对应着含有@H_301_12@ng-app@H_301_12@指令属性的那个@H_301_12@DOM@H_301_12@元素。@H_301_12@
app.run(function($rootScope){$rootScope.name="张三";});
如果页面上没有明确设定$scope@H_301_12@,@H_301_12@Angular@H_301_12@就会把数据和函数都绑定到这里,第一部分中的例子就是靠这一点成功运行的。@H_301_12@
这样,我们就可以在view@H_301_12@的任何地方访问这个@H_301_12@name@H_301_12@属性,使用模版表达式@H_301_12@{{}}@H_301_12@,像这样:@H_301_12@
{{name}}
4.2.2module
首先需要明确一下模板的概念。在我还不知道有模板这个东西的时候,曾经用js@H_301_12@拼接出很长的@H_301_12@HTML@H_301_12@字符串,然后@H_301_12@append@H_301_12@到页面中,这种方式想想真是又土又笨。后来又看到可以把@H_301_12@HTML@H_301_12@代码包裹在一个@H_301_12@<script>@H_301_12@标签中当作模板,然后按需要取来使用。@H_301_12@
在ng@H_301_12@中,模板十分简单,它就是我们页面上的@H_301_12@HTML@H_301_12@代码,不需要附加任何额外的东西。在模板中可以使用各种指令来增强它的功能,这些指令可以让你把模板和数据巧妙的绑定起来。@H_301_12@
在<html>@H_301_12@标签上多了一个属性@H_301_12@ng-app=@H_301_12@”@H_301_12@MyApp@H_301_12@”,它的作用就是用来指定@H_301_12@ng@H_301_12@的作用域是在@H_301_12@<html>@H_301_12@标签以内部分。在@H_301_12@js@H_301_12@中,我们调用@H_301_12@angular@H_301_12@对象的@H_301_12@module@H_301_12@方法来声明一个模块,模块的名字和@H_301_12@ng-app@H_301_12@的值对应。这样声明一下就可以让@H_301_12@ng@H_301_12@运行起来了。@H_301_12@
示例:
vardemoApp=angular.module('demoApp',[]);
4.2.3ng-controller
要明确创建一个$scope@H_301_12@对象,我们就要给@H_301_12@DOM@H_301_12@元素安上一个@H_301_12@controller@H_301_12@对象,使用的是@H_301_12@ng-controller@H_301_12@指令属性:@H_301_12@
<divng-controller="MyController">{{person.name}}</div>
ng-controller指令给所在的@H_301_12@DOM@H_301_12@元素创建了一个新的@H_301_12@$scope@H_301_12@对象,并将这个@H_301_12@$scope@H_301_12@对象包含进外层@H_301_12@DOM@H_301_12@元素的@H_301_12@$scope@H_301_12@对象里。在上面的例子里,这个外层@H_301_12@DOM@H_301_12@元素的@H_301_12@$scope@H_301_12@对象,就是@H_301_12@$rootScope@H_301_12@对象。这个@H_301_12@scope@H_301_12@链是这样的:@H_301_12@
所有scope@H_301_12@都遵循原型继承(@H_301_12@prototypalinheritance@H_301_12@),这意味着它们都能访问父@H_301_12@scope@H_301_12@们。对任何属性和方法,如果@H_301_12@AngularJS@H_301_12@在当前@H_301_12@scope@H_301_12@上找不到,就会到父@H_301_12@scope@H_301_12@上去找,如果在父@H_301_12@scope@H_301_12@上也没找到,就会继续向上回溯,一直到@H_301_12@$rootScope@H_301_12@上。即如果@H_301_12@controller@H_301_12@是多层嵌套的,就会从最里面一直往外找,这个@H_301_12@scope@H_301_12@链是这样的:@H_301_12@
唯一的例外:有些指令属性可以选择性地创建一个独立的scope@H_301_12@,让这个@H_301_12@scope@H_301_12@不继承它的父@H_301_12@scope@H_301_12@们,这个会在指令详解中说明。@H_301_12@
4.3ajax
$http服务是@H_301_12@Angular@H_301_12@JS的核心服务之一,它帮助我们通过XMLHttpRequest@H_301_12@对象或@H_301_12@JSONP@H_301_12@与远程@H_301_12@HTTP@H_301_12@服务进行交流。@H_301_12@
$http服务是这样一个函数:它接受一个设置对象,其中指定了如何创建@H_301_12@HTTP@H_301_12@请求;它将返回一个承诺(@H_301_12@*@H_301_12@参考@H_301_12@JavaScript@H_301_12@异步编程的@H_301_12@promise@H_301_12@模式),其中提供两个方法:@H_301_12@success@H_301_12@方法和@H_301_12@error@H_301_12@方法。@H_301_12@
$http.get({url:"../xxx.action"}).success(function(data){
alert(data);
}).error(function(){
Alert(“出错了!”);
AngularJS的AJAX@H_301_12@与@H_301_12@jquery等框架的AJAX@H_301_12@基本一致,这里就不多说了。@H_301_12@
4.4表达式
ng中的表达式与@H_301_12@javascript@H_301_12@表达式类似但是不可以划等号,它是@H_301_12@ng@H_301_12@自己定义的一套模式。表达式可以作为指令的值,如@H_301_12@ng-modle=@H_301_12@”@H_301_12@people.name@H_301_12@”、@H_301_12@ng-click=@H_301_12@”@H_301_12@showMe()@H_301_12@”,看起来是如此像字符串,故而也叫字符串表达式。也可以在标记中使用表达式,如@H_301_12@{{1+2}}@H_301_12@,或者与过滤器一起使用@H_301_12@{{1+2|currency}}@H_301_12@。在框架内部,字符串不会简单的使用@H_301_12@eval()@H_301_12@来执行,而是有一个专门的@H_301_12@$parse@H_301_12@服务来处理。在@H_301_12@ng@H_301_12@表达式中不可以使用循环语句、判断语句,事实上在模板中使用复杂的表达式也是一个不推荐的做法,这样视图与逻辑就混杂在一起了@H_301_12@
我们在使用其他模板库时,一般都会有模板的循环输出、分支输出、逻辑判断等类似的控制。
要想理解指令属性的运作,我们必须先理解表达式。在之前的例子里我们已经见过表达式,例如{{@H_301_12@user.name}}。@H_301_12@
请查看例03@H_301_12@、例@H_301_12@04@H_301_12@、例@H_301_12@05@H_301_12@。@H_301_12@
{{8+1}} 9
{{person}} {"name":"AriLerner"}
{{10*3.3|currency}} $33.00
表达式粗略来看有点像eval(javascript)@H_301_12@的结果。它们会经过@H_301_12@Angular.js@H_301_12@的处理,从而拥有以下重要而独特的性质:@H_301_12@
l所有表达式都在scope@H_301_12@这个@H_301_12@context@H_301_12@里被执行,因此可以使用所有本地@H_301_12@$scope@H_301_12@中的变量。@H_301_12@
l如果一个表达式的执行导致类型错误或引用错误,这些错误将不会被抛出。
l表达式里不允许任何控制函数流程的功能(如if/else@H_301_12@等条件语句)@H_301_12@
l表达式可接受一个或多个串联起来的过滤器。
4.5过滤器
过滤器(filter@H_301_12@)正如其名,作用就是接收一个输入,通过某个规则进行处理,然后返回处理后的结果。主要用在数据的格式化上,例如获取一个数组中的子集,对数组中的元素进行排序等。过滤器通常是伴随标记来使用的,将你@H_301_12@model@H_301_12@中的数据格式化为需要的格式。表单的控制功能主要涉及到数据验证以及表单控件的增强。@H_301_12@ng@H_301_12@内置了一些过滤器,它们是:@H_301_12@
currency(货币@H_301_12@)@H_301_12@、@H_301_12@date(@H_301_12@日期@H_301_12@)@H_301_12@、@H_301_12@filter(@H_301_12@子串匹配@H_301_12@)@H_301_12@、@H_301_12@json(@H_301_12@格式化@H_301_12@json@H_301_12@对象@H_301_12@)@H_301_12@、@H_301_12@limitTo(@H_301_12@限制个数@H_301_12@)@H_301_12@、@H_301_12@lowercase(@H_301_12@小写@H_301_12@)@H_301_12@、@H_301_12@uppercase(@H_301_12@大写@H_301_12@)@H_301_12@、@H_301_12@number(@H_301_12@数字@H_301_12@)@H_301_12@、@H_301_12@orderBy(@H_301_12@排序@H_301_12@)@H_301_12@。@H_301_12@
4.5.1过滤器使用方式@H_301_12@
总共九种。除此之外还可以自定义过滤器,这个就强大了,可以满足任何要求的数据处理。Filter还是很简单的,需要明白的是内置的@H_301_12@filter@H_301_12@如何使用,以及自己如何定义一个@H_301_12@filter@H_301_12@。@H_301_12@
1.@H_301_12@在模板中使用@H_301_12@filter@H_301_12@
我们可以直接在{{}}@H_301_12@中使用@H_301_12@filter@H_301_12@,跟在表达式后面用@H_301_12@|@H_301_12@分割,语法如下:@H_301_12@
{{expression|filter}}
也可以多个filter@H_301_12@连用,上一个@H_301_12@filter@H_301_12@的输出将作为下一个@H_301_12@filter@H_301_12@的输入:@H_301_12@
{{expression|filter1|filter2|...}} @H_301_12@
filter可以接收参数,参数用@H_301_12@:@H_301_12@进行分割,如下:@H_301_12@
{{expression|filter:argument1:argument2:...}} @H_301_12@
除了对{{}}@H_301_12@中的数据进行格式化,我们还可以在指令中使用@H_301_12@filter@H_301_12@,例如先对数组@H_301_12@array@H_301_12@进行过滤处理,然后再循环输出:@H_301_12@
<spanng-repeat="ainarray|filter"> @H_301_12@
2.在@H_301_12@controller@H_301_12@和@H_301_12@service@H_301_12@中使用@H_301_12@filter@H_301_12@
我们的js@H_301_12@代码中也可以使用过滤器,方式就是我们熟悉的依赖注入,例如我要在@H_301_12@controller@H_301_12@中使用@H_301_12@currency@H_301_12@过滤器,只需将它注入到该@H_301_12@controller@H_301_12@中即可,代码如下:@H_301_12@
app.controller('testC',function($scope,currencyFilter){
$scope.num=currencyFilter(123534);
} @H_301_12@
在模板中使用{{num}}@H_301_12@就可以直接输出@H_301_12@$123,534.00@H_301_12@了!在服务中使用@H_301_12@filter@H_301_12@也是同样的道理。@H_301_12@
如果你要在controller@H_301_12@中使用多个@H_301_12@filter@H_301_12@,并不需要一个一个注入吗,@H_301_12@ng@H_301_12@提供了一个@H_301_12@$filter@H_301_12@服务可以来调用所需的@H_301_12@filter@H_301_12@,你只需注入一个@H_301_12@$filter@H_301_12@就够了,使用方法如下:@H_301_12@
$scope.num=$filter('currency')(123534); @H_301_12@
$scope.date=$filter('date')(newDate());
可以达到同样的效果。好处是你可以方便使用不同的filter@H_301_12@了。@H_301_12@
4.5.2ng的内置过滤器@H_301_12@
ng内置了九种过滤器,使用方法都非常简单,看文档即懂。不过为了以后不去翻它的文档,我在这里还是做一个详细的记录。@H_301_12@
currency(货币@H_301_12@)@H_301_12@、@H_301_12@date(@H_301_12@日期@H_301_12@)@H_301_12@、@H_301_12@filter(@H_301_12@子串匹配@H_301_12@)@H_301_12@、@H_301_12@json(@H_301_12@格式化@H_301_12@json@H_301_12@对象@H_301_12@)@H_301_12@、@H_301_12@limitTo(@H_301_12@限制个数@H_301_12@)@H_301_12@、@H_301_12@lowercase(@H_301_12@小写@H_301_12@)@H_301_12@、@H_301_12@uppercase(@H_301_12@大写@H_301_12@)@H_301_12@、@H_301_12@number(@H_301_12@数字@H_301_12@)@H_301_12@、@H_301_12@orderBy(@H_301_12@排序@H_301_12@)@H_301_12@
1.currency(货币处理@H_301_12@)@H_301_12@
使用currency@H_301_12@可以将数字格式化为货币,默认是美元符号,你可以自己传入所需的符号,例如我传入人民币:@H_301_12@
{{num|currency:'¥@H_301_12@'}}@H_301_12@ @H_301_12@
2.date(日期格式化@H_301_12@)@H_301_12@
原生的js@H_301_12@对日期的格式化能力有限,@H_301_12@ng@H_301_12@提供的@H_301_12@date@H_301_12@过滤器基本可以满足一般的格式化要求。用法如下:@H_301_12@
{{date|date:'yyyy-MM-ddhh:mm:ssEEEE'}} @H_301_12@
参数用来指定所要的格式,yMdhmsE@H_301_12@分别表示年月日时分秒星期,你可以自由组合它们。也可以使用不同的个数来限制格式化的位数。另外参数也可以使用特定的描述性字符串,例如“@H_301_12@shortTime@H_301_12@”将会把时间格式为@H_301_12@12:05pm@H_301_12@这样的。@H_301_12@ng@H_301_12@提供了八种描述性的字符串,个人觉得这些有点多余,我完全可以根据自己的意愿组合出想要的格式,不愿意去记这么多单词@H_301_12@~@H_301_12@
3.filter(匹配子串@H_301_12@)@H_301_12@
这个名叫filter@H_301_12@的@H_301_12@filter@H_301_12@。用来处理一个数组,然后可以过滤出含有某个子串的元素,作为一个子数组来返回。可以是字符串数组,也可以是对象数组。如果是对象数组,可以匹配属性的值。它接收一个参数,用来定义子串的匹配规则。下面举个例子说明一下参数的用法,我用现在特别火的几个孩子定义了一个数组:@H_301_12@
$scope.childrenArray=[
{name:'kimi',age:3},sans-serif; font-size:16px"> {name:'cindy',age:4},sans-serif; font-size:16px"> {name:'anglar',sans-serif; font-size:16px"> {name:'shitou',age:6},sans-serif; font-size:16px"> {name:'tiantian',age:5}
$scope.func=function(e){returne.age>4;}{{childrenArray|filter:'a'}}//匹配属性值中含有@H_301_12@a@H_301_12@的@H_301_12@
{{childrenArray|filter:4}}//匹配属性值中含有@H_301_12@4@H_301_12@的@H_301_12@
{{childrenArray|filter:{name:'i'}}}//参数是对象,匹配@H_301_12@name@H_301_12@属性中含有@H_301_12@i@H_301_12@的@H_301_12@
{{childrenArray|filter:func}}//参数是函数,指定返回@H_301_12@age>4@H_301_12@的 @H_301_12@
4.json(格式化@H_301_12@json@H_301_12@对象@H_301_12@)@H_301_12@
json@H_301_12@过滤器可以把一个@H_301_12@js@H_301_12@对象格式化为@H_301_12@json@H_301_12@字符串,没有参数。这东西有什么用呢,我一般也不会在页面上输出一个@H_301_12@json@H_301_12@串啊,官网说它可以用来进行调试,嗯,是个不错的选择。或者,也可以用在@H_301_12@js@H_301_12@中使用,作用就和我们熟悉的@H_301_12@JSON.stringify()@H_301_12@一样。用法超级简单:@H_301_12@
{{jsonTest|json}}
5.limitTo(限制数组长度或字符串长度@H_301_12@)@H_301_12@
limitTo@H_301_12@过滤器用来截取数组或字符串,接收一个参数用来指定截取的长度,如果参数是负值,则从数组尾部开始截取。个人觉得这个@H_301_12@filter@H_301_12@有点鸡肋,首先只能从数组或字符串的开头@H_301_12@/@H_301_12@尾部进行截取,其次,@H_301_12@js@H_301_12@原生的函数就可以代替它了,看看怎么用吧:@H_301_12@
{{childrenArray|limitTo:2}}//将会显示数组中的前两项 @H_301_12@
6.lowercase(小写@H_301_12@)@H_301_12@
把数据转化为全部小写。太简单了,不多解释。同样是很鸡肋的一个filter@H_301_12@,没有参数,只能把整个字符串变为小写,不能指定字母。怎么用我都懒得写了。@H_301_12@
7.uppercase(大写@H_301_12@)@H_301_12@
同上。
8.number(格式化数字@H_301_12@)@H_301_12@
number@H_301_12@过滤器可以为一个数字加上千位分割,像这样,@H_301_12@123,456,789@H_301_12@。同时接收一个参数,可以指定@H_301_12@float@H_301_12@类型保留几位小数:@H_301_12@
{{num|number:2}} @H_301_12@
9.orderBy(排序@H_301_12@)@H_301_12@
orderBy@H_301_12@过滤器可以将一个数组中的元素进行排序,接收一个参数来指定排序规则,参数可以是一个字符串,表示以该属性名称进行排序。可以是一个函数,定义排序属性。还可以是一个数组,表示依次按数组中的属性值进行排序(若按第一项比较的值相等,再按第二项比较),还是拿上面的孩子数组举例:@H_301_12@
<div>{{childrenArray|orderBy:'age'}}</div>//按@H_301_12@age@H_301_12@属性值进行排序,若是@H_301_12@-age@H_301_12@,则倒序@H_301_12@
<div>{{childrenArray|orderBy:orderFunc}}</div>//按照函数的返回值进行排序@H_301_12@
<div>{{childrenArray|orderBy:['age','name']}}</div>//如果@H_301_12@age@H_301_12@相同,按照@H_301_12@name@H_301_12@进行排序 内置的过滤器介绍完了,写的我都快睡着了。。。正如你所看到的,@H_301_12@ng@H_301_12@内置的过滤器也并不是万能的,事实上好多都比较鸡肋。更个性化的需求就需要我们来定义自己的过滤器了,下面来看看如何自定义过滤器。@H_301_12@
4.5.3自定义过滤器及示例@H_301_12@
filter@H_301_12@的自定义方式也很简单,使用@H_301_12@module@H_301_12@的@H_301_12@filter@H_301_12@方法,返回一个函数,该函数接收@H_301_12@
输入值,并返回处理后的结果。话不多说,我们来写一个看看。比如我需要一个过滤器,它可以返回一个数组中下标为奇数的元素,代码如下:
app.filter('odditems',sans-serif; font-size:16px"> returnfunction(inputArray){
vararray=[];
for(vari=0;i<inputArray.length;i++){
if(i%2!==0){
array.push(inputArray[i]);
returnarray;
}); @H_301_12@
格式就是这样,你的处理逻辑就写在内部的那个闭包函数中。你也可以让自己的过滤器接收参数,参数就定义在return@H_301_12@的那个函数中,作为第二个参数,或者更多个参数也可以。@H_301_12@
自定义过滤器实例(例04@H_301_12@):@H_301_12@
/*Viewhtml*/
Firstname:@H_301_12@<inputng-model="user.firstName"/><br/>@H_301_12@
Lastname:@H_301_12@<inputng-model="user.lastName"/><br/>@H_301_12@
Firstname:@H_301_12@{{user.firstName}}Lastname@H_301_12@:@H_301_12@{{user.lastName}}<br/>@H_301_12@
Fullname:@H_301_12@{{user|flFullname}}<br/>@H_301_12@
Fullname:@H_301_12@{{user|flFullname:"@H_301_12@-@H_301_12@"}}<br/>@H_301_12@
Fullname:@H_301_12@{{user|flFullname:"@H_301_12@•@H_301_12@"|uppercase}}@H_301_12@
/*Controllerjs*/
demoApp.filter("flFullname",sans-serif; font-size:16px"> returnfunction(user,sep){
sep=sep||"";
user=user||{};
fullName="";
if(user.firstName){fullName+=user.firstName;}
if(user.lastName){fullName=fullName+sep+user.lastName;}
if(fullName&&fullName.length>0){returnfullName;
}else{return"";}
});
4.6指令(directive)
通过使用模板,我们可以把model@H_301_12@和@H_301_12@controller@H_301_12@中的数据组装起来呈现给浏览器,还可以通过数据绑定,实时更新视图,让我们的页面变成动态的。@H_301_12@
模板中可以使用的东西包括以下四种:
1.指令@H_301_12@(directive)@H_301_12@:@H_301_12@ng@H_301_12@提供的或者自定义的标签和属性,用来增强@H_301_12@HTML@H_301_12@表现力;@H_301_12@
2.标记@H_301_12@(markup)@H_301_12@:即双大括号@H_301_12@{{}}@H_301_12@,可将数据单向绑定到@H_301_12@HTML@H_301_12@中;@H_301_12@
3.过滤器@H_301_12@(filter)@H_301_12@:用来格式化输出数据;@H_301_12@
其中,指令无疑是使用量最大的,ng@H_301_12@内置了很多指令用来控制模板,如@H_301_12@ng-repeat@H_301_12@,@H_301_12@ng-class@H_301_12@,也有很多指令来帮你完成业务逻辑,如@H_301_12@ng-controller,ng-model@H_301_12@。@H_301_12@
指令的几种使用方式如下:
l作为标签:<my-dir></my-dir>@H_301_12@
l作为属性:<spanmy-dir="exp"></span>@H_301_12@
l作为注释:<!--directive:my-direxp-->@H_301_12@
l作为类名:<spanclass="my-dir:exp;"></span>@H_301_12@
4.6.1样式相关的指令@H_301_12@
既然模板就是普通的HTML@H_301_12@,那我首要关心的就是样式的控制,元素的定位、字体、背景色等等如何可以灵活控制。下面来看看常用的样式控制指令。@H_301_12@
1.ng-class
ng-class用来给元素绑定类名,其表达式的返回值可以是以下三种:@H_301_12@
l类名字符串,可以用空格分割多个类名,如’redtextboldtext@H_301_12@’;@H_301_12@
l类名数组,数组中的每一项都会层叠起来生效;
l一个名值对应的map@H_301_12@,其键值为类名,值为@H_301_12@boolean@H_301_12@类型,当值为@H_301_12@true@H_301_12@时,该类会被加在元素上。@H_301_12@
下面来看一个使用map@H_301_12@的例子:@H_301_12@
ng-class测试@H_301_12@
红色加粗删除线
map:{redtext:{{red}},boldtext:{{bold}},striketext:{{strike}}}
如果你想拼接一个类名出来,可以使用插值表达式,如:
<divclass=@H_301_12@”@H_301_12@{{style}}text@H_301_12@”@H_301_12@>@H_301_12@字体样式测试@H_301_12@</div>@H_301_12@
然后在controller@H_301_12@中指定@H_301_12@style@H_301_12@的值:@H_301_12@
$scope.style=@H_301_12@‘@H_301_12@red@H_301_12@’@H_301_12@;@H_301_12@
注意我用了class@H_301_12@而不是@H_301_12@ng-class@H_301_12@,这是不可以对换的,官方的文档也未做说明,姑且认为这是@H_301_12@ng@H_301_12@的语法规则吧。@H_301_12@
与ng-class@H_301_12@相近的,@H_301_12@ng@H_301_12@还提供了@H_301_12@ng-class-odd@H_301_12@、@H_301_12@ng-class-even@H_301_12@两个指令,用来配合@H_301_12@ng-repeat@H_301_12@分别在奇数列和偶数列使用对应的类。这个用来在表格中实现隔行换色再方便不过了。@H_301_12@
2.ng-style
ng-style@H_301_12@用来绑定元素的@H_301_12@css@H_301_12@样式,其表达式的返回值为一个@H_301_12@js@H_301_12@对象,键为@H_301_12@css@H_301_12@样式名,值为该样式对应的合法取值。用法比较简单:@H_301_12@
<divng-style="{color:'red'}">ng-style测试@H_301_12@</div>@H_301_12@
<divng-style="style">ng-style测试@H_301_12@</div>@H_301_12@
$scope.style={color:'red'}; @H_301_12@
3.ng-show,@H_301_12@ng-hide@H_301_12@
对于比较常用的元素显隐控制,ng@H_301_12@也做了封装,@H_301_12@ng-show@H_301_12@和@H_301_12@ng-hide@H_301_12@的值为@H_301_12@boolean@H_301_12@类型的表达式,当值为@H_301_12@true@H_301_12@时,对应的@H_301_12@show@H_301_12@或@H_301_12@hide@H_301_12@生效。框架会用@H_301_12@display:block@H_301_12@和@H_301_12@display:none@H_301_12@来控制元素的显隐。@H_301_12@
4.6.2表单控件功能相关指令@H_301_12@
对于常用的表单控件功能,ng@H_301_12@也做了封装,方便灵活控制。@H_301_12@
ng-checked@H_301_12@控制@H_301_12@radio@H_301_12@和@H_301_12@checkBox@H_301_12@的选中状态@H_301_12@
ng-selected@H_301_12@控制下拉框的选中状态@H_301_12@
ng-disabled@H_301_12@控制失效状态@H_301_12@
ng-multiple@H_301_12@控制多选@H_301_12@
ng-readonly@H_301_12@控制只读状态@H_301_12@
以上指令的取值均为boolean@H_301_12@类型,当值为@H_301_12@true@H_301_12@时相关状态生效,道理比较简单就不多做解释。注意:上面的这些只是单向绑定,即只是从数据到模板,不能反作用于数据。要双向绑定,还是要使用@H_301_12@ng-model@H_301_12@。@H_301_12@
4.6.3事件绑定相关指令@H_301_12@
事件绑定是javascrpt@H_301_12@中比较重要的一部分内容,@H_301_12@ng@H_301_12@对此也做了详细的封装,正如我们之前使用过的@H_301_12@ng-click@H_301_12@一样,事件的指令如下:@H_301_12@
ng-click
ng-change@H_301_12@
ng-dblclick@H_301_12@
ng-mousedown@H_301_12@
ng-mouseenter@H_301_12@
ng-mouseleave@H_301_12@
ng-mousemove@H_301_12@
ng-mouseup@H_301_12@
ng-submit@H_301_12@
<selectng-change=”@H_301_12@change($event)@H_301_12@”@H_301_12@></select>@H_301_12@ @H_301_12@
然后在controller@H_301_12@中定义如下:@H_301_12@
$scope.change=function($event){
alert($event.target);
//……………………
在模板中可以用变量$event@H_301_12@将事件对象传递到@H_301_12@controller@H_301_12@中。@H_301_12@
对于ng@H_301_12@的这种设计,一些人有所质疑,视图与事件绑定混在一起到底好不好?我们不是要讲究视图与逻辑分离吗?如此一来,把事件的绑定又变回了内联的,岂不是历史的倒退。我也一样对此表示不解,因为不写@H_301_12@onclick@H_301_12@已经很多年。。。但既然已经存在了,我们不妨往合理的方向上想一想,或许@H_301_12@ng@H_301_12@的设计者压根就不想让模板成为单纯的视图层,本来就是想增强@H_301_12@HTML@H_301_12@,让它有一点业务能力。这么想的话似乎也能想通,好吧,先欺骗一下自己吧@H_301_12@~@H_301_12@
4.6.4特殊的@H_301_12@ng-src@H_301_12@和@H_301_12@ng-href@H_301_12@
在说明这两个指令的特殊之前,需要先了解一下ng@H_301_12@的启动及执行过程,如下图:@H_301_12@
1)浏览器加载静态@H_301_12@HTML@H_301_12@文件并解析为@H_301_12@DOM@H_301_12@;@H_301_12@
2)@H_301_12@浏览器加载@H_301_12@angular.js@H_301_12@文件;@H_301_12@
3)angular@H_301_12@监听@H_301_12@DOMContentLoaded@H_301_12@事件,监听到时开始启动;@H_301_12@
4)angular@H_301_12@寻找@H_301_12@ng-app@H_301_12@指令,确定作用范围;@H_301_12@
5)@H_301_12@找到@H_301_12@app@H_301_12@中定义的@H_301_12@Module@H_301_12@使用@H_301_12@$injector@H_301_12@服务进行依赖注入;@H_301_12@
6)@H_301_12@根据@H_301_12@$injector@H_301_12@服务创建@H_301_12@$compile@H_301_12@服务用于编译;@H_301_12@
7)$compile@H_301_12@服务编译@H_301_12@DOM@H_301_12@中的指令、过滤器等;@H_301_12@
8)@H_301_12@使用@H_301_12@ng-init@H_301_12@指令,将作用域中的变量进行替换;@H_301_12@
9)@H_301_12@最后生成了我们在最终视图。@H_301_12@
可以看到,ng@H_301_12@框架是在@H_301_12@DOMcontent@H_301_12@加载完毕后才开始发挥作用。假如我们模板中有一张图片如下:@H_301_12@
<imgsrc="http://m.cnblogs.com/142260/”@H_301_12@{{imgUrl}}@H_301_12@”@H_301_12@/>@H_301_12@@H_301_12@
那么在页面开始加载到ng@H_301_12@编译完成之前,页面上会一直显示一张错误的图片,因为路径@H_301_12@{{imgUrl}}@H_301_12@还未被替换。@H_301_12@
为了避免这种情况,我们使用ng-src@H_301_12@指令,这样在路径被正确得到之前就不会显示找不到图片。同理,@H_301_12@<a>@H_301_12@标签的@H_301_12@href@H_301_12@属性也需要换成@H_301_12@ng-href@H_301_12@,这样页面上就不会先出现一个地址错误的链接。@H_301_12@
顺着这个思路再多想一点,我们在模板中使用{{}}@H_301_12@显示数据时,在@H_301_12@ng@H_301_12@编译完成之前页面上岂不是会显示出大括号及里面的表达式?确实是这样。为了避免这个,@H_301_12@ng@H_301_12@中有一个与@H_301_12@{{}}@H_301_12@等同的指令@H_301_12@:ng-bind@H_301_12@,同样用于单向绑定,在页面刚加载的时候就不会显示出对用户无用的数据了。尽管这样你可能不但没舒心反而更纠结了,@H_301_12@{{}}@H_301_12@那么好用易理解,还不能用了不成?好消息是我们依然可以使用。因为我编写的是单页面应用,页面只会在加载@H_301_12@index.html@H_301_12@的时@H_301_12@
候出这个问题,只需在index.html@H_301_12@中的模板中换成@H_301_12@ng-bind@H_301_12@就行。其他的模板是我们动态加载的,就可以放心使用@H_301_12@{{}}@H_301_12@了。@H_301_12@
4.6.5自定义指令示例@H_301_12@
下面我们来解析下指令的例子(例07@H_301_12@)。@H_301_12@
1.首先,我们定义一个名为@H_301_12@userInfo的指令:
demoApp.directive('userInfo',sans-serif; font-size:16px"> templateUrl:'userInfoTemplate.html',sans-serif; font-size:16px"> transclude:true,sans-serif; font-size:16px"> scope:{
mytitle:'=etitle'
link:function(scope,element,attrs){
scope.showText=false;
scope.toggleText=function(){
scope.showText=!scope.showText;
})
Restrict为@H_301_12@'E':用作标签;replace为true:用模板替换当前标签;transclude为true:将当前元素的内容转移到模板中;scope为{mytitle:'=etitle'}:定义一个名为mytitle@H_301_12@的@H_301_12@MODEL@H_301_12@,其值指向当前元素的@H_301_12@etitle@H_301_12@属性;@H_301_12@templateUrl为'userInfoTemplate.html':模板内容为ng-template定义ID@H_301_12@为@H_301_12@userInfoTemplate.html的内容;link:指定所包含的行为。其具体的说明及其他参数,请参考:6.2@H_301_12@指令详解。@H_301_12@
2.userInfoTemplate.html模板为:
<scripttype="text/ng-template"id="userInfoTemplate.html">
<divclass="myBox">
<divclass="mytitle"style="cursor:pointer;"ng-click="toggleText()">
{{mytitle}}
<divng-transcludeng-show="showText">
</script>
将当前元素的内容添加到有ng-transclude属性的这个DIV@H_301_12@下,默认是隐藏的。@H_301_12@
3.Controller信息:@H_301_12@
demoApp.controller("test7Controller",function($scope){
$scope.title='个人简介@H_301_12@';@H_301_12@
$scope.text='大家好,我正在研究@H_301_12@AngularJs@H_301_12@,欢迎大家与我交流。@H_301_12@';@H_301_12@
$scope.updateInfo=function(){
$scope.title='个人信息@H_301_12@';@H_301_12@
$scope.text='大家好,今天天气真好!@H_301_12@';@H_301_12@
4.指令使用方式(@H_301_12@View@H_301_12@信息)为:@H_301_12@
<user-infoetitle="title">{{text}}</user-info>
Etitle指向Controller@H_301_12@中的@H_301_12@$scope.title@H_301_12@。注意命名方式:指令名为@H_301_12@userInfo@H_301_12@,对应的标签为@H_301_12@user-info@H_301_12@。@H_301_12@
4.7服务(service@H_301_12@)@H_301_12@
4.7.1服务介绍@H_301_12@
服务这个概念其实并不陌生,在其他语言中如Java@H_301_12@便有这样的概念,其作用就是对外提供某个特定的功能,如消息服务,文件压缩服务等,是一个独立的模块。@H_301_12@ng@H_301_12@的服务是这样定义的:@H_301_12@
Angularservicesaresingletonsobjectsorfunctionsthatcarryoutspecifictaskscommontowebapps.
首先是一个单例,即无论这个服务被注入到任何地方,对象始终只有一个实例。
其次这与我们自己定义一个function@H_301_12@然后在其他地方调用不同,因为服务被定义在一个模块中,所以其使用范围是可以被我们管理的。@H_301_12@ng@H_301_12@的避免全局变量污染意识非常强。@H_301_12@
ng@H_301_12@提供了很多内置的服务,可以到@H_301_12@API@H_301_12@中查看@H_301_12@http://docs.angularjs.org/api/@H_301_12@。知道了概念,我们来拉一个@H_301_12@service@H_301_12@出来溜溜,看看到底是个什么用法。 @H_301_12@
我们在controller@H_301_12@中直接声明@H_301_12@$location@H_301_12@服务,这依靠@H_301_12@ng@H_301_12@的依赖注入机制。@H_301_12@$location@H_301_12@提供地址栏相关的服务,我们在此只是简单的获取当前的地址。@H_301_12@
服务的使用是如此简单,我们可以把服务注入到controller@H_301_12@、指令或者是其他服务中。@H_301_12@
4.7.2自定义服务@H_301_12@
如同指令一样,系统内置的服务以$@H_301_12@开头,我们也可以自己定义一个服务。定义服务的方式有如下几种:@H_301_12@
l使用系统内置的$provide@H_301_12@服务;@H_301_12@
l使用Module@H_301_12@的@H_301_12@factory@H_301_12@方法;@H_301_12@
l使用Module@H_301_12@的@H_301_12@service@H_301_12@方法。@H_301_12@
下面通过一个小例子来分别试验一下。我们定义一个名为remoteData@H_301_12@服务,它可以从远程获取数据,这也是我们在程序中经常使用的功能。不过我这里没有远程服务器,就写死一点数据模拟一下。@H_301_12@
//使用@H_301_12@$provide@H_301_12@来定义@H_301_12@
varapp=angular.module('MyApp',[],function($provide){
$provide.factory('remoteData',sans-serif; font-size:16px"> vardata={name:'n',value:'v'};
returndata;
//使用@H_301_12@factory@H_301_12@方法@H_301_12@
app.factory('remoteData',sans-serif; font-size:16px"> //使用@H_301_12@service@H_301_12@方法@H_301_12@
app.service('remoteData',sans-serif; font-size:16px"> this.name='n';
this.value='v';
Module的@H_301_12@factory@H_301_12@和@H_301_12@$provide@H_301_12@的@H_301_12@factory@H_301_12@方法是一模一样的,从官网文档看它们其实就是一回事。至于@H_301_12@Module@H_301_12@内部是如何调用的,我此处并不打算深究,我只要知道怎么用就好了。@H_301_12@
再看Module@H_301_12@的@H_301_12@service@H_301_12@方法,它没有@H_301_12@return@H_301_12@任何东西,是因为@H_301_12@service@H_301_12@方法本身返回一个构造器,系统会自动使用@H_301_12@new@H_301_12@关键字来创建出一个对象。所以我们看到在构造器函数内可以使用@H_301_12@this@H_301_12@,这样调用该服务的地方便可以直接通过@H_301_12@remoteData.name@H_301_12@来访问数据了。@H_301_12@
4.7.3管理服务的依赖关系@H_301_12@
服务与服务中间可以有依赖关系,例如我们这里定义一个名为validate@H_301_12@的服务,它的作用是验证数据是否合法,它需要依赖我们从远程获取数据的服务@H_301_12@remoteData@H_301_12@。代码如下:@H_301_12@
在factory@H_301_12@的参数中,我们可以直接传入服务@H_301_12@remoteData@H_301_12@,@H_301_12@ng@H_301_12@的依赖注入机制便帮我们做好了其他工作。不过一定要保证这个参数的名称与服务名称一致,@H_301_12@ng@H_301_12@是根据名称来识别的。若参数的名次与服务名称不一致,你就必须显示的声明一下,方式如下:@H_301_12@
app.factory('validate',['remoteData',function(remoteDataService){
returnfunction(){
if(remoteDataService.name=='n'){
alert('验证通过@H_301_12@');@H_301_12@
}]); @H_301_12@
我们在controller@H_301_12@中注入服务也是同样的道理,使用的名称需要与服务名称一致才可以正确注入。否则,你必须使用@H_301_12@$inject@H_301_12@来手动指定注入的服务。比如:@H_301_12@
functiontestC(scope,rd){
scope.getData=function(){
alert('name:@H_301_12@'+rd.name+'value@H_301_12@:@H_301_12@'+rd.value);@H_301_12@
testC.$inject=['$scope','remoteData'];
在controller@H_301_12@中注入服务,也可以在定义@H_301_12@controller@H_301_12@时使用数组作为第二个参数,在此处@H_301_12@
把服务注入进去,这样在函数体中使用不一致的服务名称也是可以的,不过要确保注入的顺序是一致的,如:
$scope.getData=function(){
}]);
4.7.4自定义服务示例@H_301_12@
接下来让我们看下例子(例08@H_301_12@自定义服务)代码,自定义@H_301_12@userService服务:
demoApp.factory('userService',['$http',function($http){
vardoGetUser=function(userId,path){
//return$http({
//method:'JSONP',sans-serif; font-size:16px"> //url:path
//});
/*手动指定数据@H_301_12@*/@H_301_12@
vardata={userId:"woshishui",userName:"我是谁@H_301_12@",userInfo:"@H_301_12@我是谁!我是谁!@H_301_12@"};;@H_301_12@
if(userId=='zhangsan'){
data={userId:"zhangsan",userName:"张三@H_301_12@",userInfo:"@H_301_12@我是张三,我为自己@H_301_12@"};@H_301_12@
}elseif(userId=='lisi'){
data={userId:"lisi",userName:"李四@H_301_12@",userInfo:"@H_301_12@我是李四,我为卿狂!@H_301_12@"};@H_301_12@
/*userService对外暴露的函数,可有多个@H_301_12@*/@H_301_12@
getUser:function(userId){
returndoGetUser(userId,'../xxx/xxx.action');
我们创建了一个只有一个方法的userService,getUser为这个服务从后台获取用户信息的函数,并且对外暴露。当然,由于这是一个静态的例子,无法访问后台,那么我们便制定其返回的数据。
然后我们把这个服务添加到我们的controller@H_301_12@中。我们建立一个@H_301_12@controller@H_301_12@并加载(或者注入)@H_301_12@userService作为运行时依赖,我们把service@H_301_12@的名字作为参数传递给@H_301_12@controller@H_301_12@函数:@H_301_12@
demoApp.controller("test8Controller",userService){
$scope.articles=[{
title:"爱飞像风@H_301_12@",@H_301_12@
userId:"zhangsan",sans-serif; font-size:16px"> userName:"张三@H_301_12@"@H_301_12@
title:"无法停止的雨@H_301_12@",sans-serif; font-size:16px"> userId:"lisi",sans-serif; font-size:16px"> userName:"李四@H_301_12@"@H_301_12@
}];
$scope.showUserInfo=false;//显示作者详细信息开关@H_301_12@
$scope.currentUser={};//当前选中的作者@H_301_12@
$scope.getUserInfo=function(userId){
$scope.currentUser=userService.getUser(userId);
//调用@H_301_12@userService@H_301_12@的@H_301_12@getUser@H_301_12@函数@H_301_12@
$scope.showUserInfo=true;
setTimeout(function(){//定时器:隐藏作者详细信息@H_301_12@
$scope.showUserInfo=false;
我们的userService注入到我们的test8Controller后,我们就可以像使用其他服务(我们前面提到的$http@H_301_12@服务)一样的使用@H_301_12@userService了。
相关的HTML@H_301_12@代码如下:@H_301_12@
/*ViewHTML*/
<trng-repeat="article_inarticles">
<td>
{{article_.title}}
</td>
<ahref="javascript:void(0);" target="_blank" rel="nofollow">
</tr>
......
<divng-show="showUserInfo">
用户ID@H_301_12@:@H_301_12@{{currentUser.userId}}<br/>@H_301_12@
用户名:{{currentUser.userName}}<br/>@H_301_12@
用户简介:{{currentUser.userInfo}}<br/>@H_301_12@
</div>
4.8依赖注入DI
通过依赖注入,ng@H_301_12@想要推崇一种声明式的开发方式,即当我们需要使用某一模块或服务时,不需要关心此模块内部如何实现,只需声明一下就可以使用了。在多处使用只需进行多次声明,大大提高可复用性。@H_301_12@
比如我们的controller@H_301_12@,在定义的时候用到一个@H_301_12@$scope@H_301_12@参数。@H_301_12@
@H_301_12@
如果我们在此处还需操作其他的东西,比如与浏览器地址栏进行交互。我们只需再多添
一个参数$location@H_301_12@进去:@H_301_12@
@H_301_12@
这样便可以通过$location@H_301_12@来与地址栏进行交互了,我们仅仅是声明了一下,所需的其他代码,框架已经帮我们注入了。我们很明显的感觉到了这个函数已经不是常规意义上的@H_301_12@javascript@H_301_12@函数了,在常规的函数中,把形参换一个名字照样可以运行,但在此处若是把@H_301_12@$scope@H_301_12@换成别的名字,程序便不能运行了。因为这是已经定义好的服务名称。@H_301_12@
这便是依赖注入机制。顺理成章的推断,我们可以自己定义模块和服务,然后在需要的地方进行声明,由框架来替我们注入。
来看下我们如何定义一个服务:
app.factory('tpls',sans-serif; font-size:16px"> return['tpl1','tpl2','tpl3','tpl4'];
看上去相当简单,是因为我在这里仅仅是直接返回一个数组。在实际应用中,这里应该是需要向服务器发起一个请求,来获取到这些模板们。服务的定义方式有好几种,包括使用provider@H_301_12@方法、使用@H_301_12@factory@H_301_12@方法,使用@H_301_12@service@H_301_12@方法。它们之间的区别暂且不关心。我们现在只要能创建一个服务出来就可以了。我使用了@H_301_12@factory@H_301_12@方法。一个需要注意的地方是,框架提供的服务名字都是由@H_301_12@$@H_301_12@开头的,所以我们自己定义的最好不要用@H_301_12@$@H_301_12@开头,防止发生命名冲突。@H_301_12@
定义好一个服务后,我们就可以在控制器中声明使用了,如下:
$scope.question=questionModel;
$scope.nowTime=newDate().valueOf();
$scope.templates=tpls;//赋值到@H_301_12@$scope@H_301_12@中@H_301_12@
$scope.addOption=function(){
varo={content:''};
$scope.question.options.push(o);
$scope.delOption=function(index){
$scope.question.options.splice(index,1);
此时,若在模板中书写如下代码,我们便可以获取到服务tpls@H_301_12@所提供的数据了:@H_301_12@
模板:@H_301_12@
<ahref="javascript:void(0);" target="_blank" rel="nofollow">
4.9路由@H_301_12@(route@H_301_12@)@H_301_12@
在谈路由机制前有必要先提一下现在比较流行的单页面应用,就是所谓的singlepageAPP@H_301_12@。为了实现无刷新的视图切换,我们通常会用@H_301_12@ajax@H_301_12@请求从后台取数据,然后套上@H_301_12@HTML@H_301_12@模板渲染在页面上,然而@H_301_12@ajax@H_301_12@的一个致命缺点就是导致浏览器后退按钮失效,尽管我们可以在页面上放一个大大的返回按钮,让用户点击返回来导航,但总是无法避免用户习惯性的点后退。解决此问题的一个方法是使用@H_301_12@hash@H_301_12@,监听@H_301_12@hashchange@H_301_12@事件来进行视图切换,另一个方法是用@H_301_12@HTML5@H_301_12@的@H_301_12@historyAPI@H_301_12@,通过@H_301_12@pushState()@H_301_12@记录操作历史,监听@H_301_12@popstate@H_301_12@事件来进行视图切换,也有人把这叫@H_301_12@pjax@H_301_12@技术。@H_301_12@基本流程如下:
如此一来,便形成了通过地址栏进行导航的深度链接(deeplinking@H_301_12@),也就是我们所需要的路由机制。通过路由机制,一个单页应用的各个视图就可以很好的组织起来了。@H_301_12@
4.9.1ngRoute内容@H_301_12@
ng@H_301_12@的路由机制是靠@H_301_12@ngRoute@H_301_12@提供的,通过@H_301_12@hash@H_301_12@和@H_301_12@history@H_301_12@两种方式实现了路由,可以检测浏览器是否支持@H_301_12@history@H_301_12@来灵活调用相应的方式。@H_301_12@ng@H_301_12@的路由@H_301_12@(ngRoute)@H_301_12@是一个单独的模块,包含以下内容:@H_301_12@
l服务$routeProvider@H_301_12@用来定义一个路由表,即地址栏与视图模板的映射@H_301_12@
l服务$routeParams@H_301_12@保存了地址栏中的参数,例如@H_301_12@{id:1,name:'tom'}@H_301_12@
l服务$route@H_301_12@完成路由匹配,并且提供路由相关的属性访问及事件,如访问当前路由对应的@H_301_12@controller@H_301_12@
l指令ngView@H_301_12@用来在主视图中指定加载子视图的区域@H_301_12@
以上内容再加上$location@H_301_12@服务,我们就可以实现一个单页面应用了。下面来看一下具体如何使用这些内容。@H_301_12@
4.9.2ng的路由机制@H_301_12@
第一步:引入文件和依赖
ngRoute@H_301_12@模块包含在一个单独的文件中,所以第一步需要在页面上引入这个文件,如下:@H_301_12@
<scriptsrc="http://code.angularjs.org/1.2.8/angular.min.js" rel="nofollow"/>
<scriptsrc="http://code.angularjs.org/1.2.8/angular-route.min.js" rel="nofollow"/>
光引入还不够,我们还需在模块声明中注入对ngRoute@H_301_12@的依赖,如下:@H_301_12@
@H_301_12@
完成了这些,我们就可以在模板或是controller@H_301_12@中使用上面的服务和指令了。下面我们需要定义一个路由表。@H_301_12@
第二步:定义路由表
$routeProvider@H_301_12@提供了定义路由表的服务,它有两个核心方法,@H_301_12@when(path,route)@H_301_12@和@H_301_12@otherwise(params)@H_301_12@,先看一下核心中的核心@H_301_12@when(path,route)@H_301_12@方法。@H_301_12@
when(path,route)@H_301_12@方法接收两个参数,@H_301_12@path@H_301_12@是一个@H_301_12@string@H_301_12@类型,表示该条路由规则所匹配的路径,它将与地址栏的内容@H_301_12@($location.path)@H_301_12@值进行匹配。如果需要匹配参数,可以在@H_301_12@path@H_301_12@中使用冒号加名称的方式,如:@H_301_12@path@H_301_12@为@H_301_12@/show/:name@H_301_12@,如果地址栏是@H_301_12@/show/tom@H_301_12@,那么参数@H_301_12@name@H_301_12@和所对应的值@H_301_12@tom@H_301_12@便会被保存在@H_301_12@$routeParams@H_301_12@中,像这样:@H_301_12@{name:tom}@H_301_12@。我们也可以用@H_301_12@*@H_301_12@进行模糊匹配,如:@H_301_12@/show*/:name@H_301_12@将匹配@H_301_12@/showInfo/tom@H_301_12@。@H_301_12@
route@H_301_12@参数是一个@H_301_12@object@H_301_12@,用来指定当@H_301_12@path@H_301_12@匹配后所需的一系列配置项,包括以下内容:@H_301_12@
lcontroller//function或@H_301_12@string@H_301_12@类型。在当前模板上执行的@H_301_12@controller@H_301_12@函数,生成新的@H_301_12@scope@H_301_12@;@H_301_12@
lcontrollerAs//string类型,为@H_301_12@controller@H_301_12@指定别名;@H_301_12@
ltemplate//string或@H_301_12@function@H_301_12@类型,视图@H_301_12@z@H_301_12@所用的模板,这部分内容将被@H_301_12@ngView@H_301_12@引用;@H_301_12@
ltemplateUrl//string或@H_301_12@function@H_301_12@类型,当视图模板为单独的@H_301_12@html@H_301_12@文件或是使用了@H_301_12@<scripttype="text/ng-template">@H_301_12@定义模板时使用;@H_301_12@
lresolve//指定当前@H_301_12@controller@H_301_12@所依赖的其他模块;@H_301_12@
lredirectTo//重定向的地址。@H_301_12@
最简单情况,我们定义一个html@H_301_12@文件为模板,并初始化一个指定的@H_301_12@controller@H_301_12@:@H_301_12@
functionemailRouteConfig($routeProvider){
$routeProvider.when('/show',sans-serif; font-size:16px"> controller:ShowController,sans-serif; font-size:16px"> templateUrl:'show.html'
}).
when('/put/:name',sans-serif; font-size:16px"> controller:PutController,sans-serif; font-size:16px"> templateUrl:'put.html'
}; @H_301_12@
otherwise(params)方法对应路径匹配不到时的情况,这时候我们可以配置一个@H_301_12@redirectTo@H_301_12@参数,让它重定向到@H_301_12@404@H_301_12@页面或者是首页。@H_301_12@
第三步:在主视图模板中指定加载子视图的位置
我们的单页面程序都是局部刷新的,那这个“局部”是哪里呢,这就轮到ngView@H_301_12@出马了,只需在模板中简单的使用此指令,在哪里用,哪里就是“局部”。例如:@H_301_12@
<divng-view></div> 或:@H_301_12@<ng-view></ng-view>@H_301_12@ @H_301_12@
我们的子视图将会在此处被引入进来。完成这三步后,你的程序的路由就配置好了。
4.9.3路由示例@H_301_12@
下面我们将用一个例子(例09@H_301_12@)来说明路由的使用方式及步骤:@H_301_12@
1.为@H_301_12@demoApp@H_301_12@添加一个路由,代码如下:@H_301_12@
demoApp.config(['$routeProvider',function($routeProvider){
$routeProvider.when('/list',{
templateUrl:'route/list.html',
controller:'routeListController'
}).when('/list/:id',sans-serif; font-size:16px"> templateUrl:'route/detail.html',sans-serif; font-size:16px"> controller:'routeDetailController'
}).otherwise({
redirectTo:'/list'
});
}]);
/list对应为:route/list.html页面,显示用户列表;/list/:id对应于route/detail.html页面,显示用户详细信息。
2.为@H_301_12@list.html和detail.html分别声明Controller@H_301_12@:@H_301_12@routeListController和routeDetailController。
demoApp.controller('routeListController',sans-serif; font-size:16px"> $scope.users=[{userId:"zhangsan",userInfo:"@H_301_12@我是张三,我为自己带盐!@H_301_12@"},sans-serif; font-size:16px"> {userId:"lisi",userInfo:"@H_301_12@我是李四,我为卿狂!@H_301_12@"},sans-serif; font-size:16px"> {userId:"woshishui",userInfo:"@H_301_12@我是谁!我是谁!我是谁!@H_301_12@"}];@H_301_12@
demoApp.controller('routeDetailController',$routeParams,sans-serif; font-size:16px"> $scope.userDetail=userService.getUser($routeParams.id);
routeDetailController中如上面提到的一样,注入了userService服务,在这里直接拿来用。
3.创建@H_301_12@list.html和detail.html页面,代码如下:
<hr/>
<h3>Route:List.html(用户列表页面)@H_301_12@</h3>@H_301_12@
<ling-repeat="userinusers">
<ahref="http://m.cnblogs.com/142260/3817063.html?full=1#/list/{{user.userId}}" target="_blank" rel="nofollow">
<h3>Route:detail.html(用户详细信息页面)@H_301_12@</h3>@H_301_12@
<h3>用户名:@H_301_12@<spanstyle="color:red;">{{userDetail.userName}}</span></h3>@H_301_12@
<div>
<span>用户@H_301_12@ID@H_301_12@:@H_301_12@{{userDetail.userId}}</span><span>@H_301_12@用户名:@H_301_12@{{userDetail.userName}}</span>@H_301_12@
用户简介:<span>{{userDetail.userInfo}}</span>@H_301_12@
<ahref="http://m.cnblogs.com/142260/3817063.html?full=1#/list" target="_blank" rel="nofollow">返回</a>@H_301_12@
4.路由局部刷新位置:@H_301_12@
<h1>AngularJS路由(@H_301_12@Route@H_301_12@)示例@H_301_12@</h1>@H_301_12@
<divng-view></div>
4.10NG动画效果@H_301_12@
4.10.1NG动画效果简介@H_301_12@
NG动画效果,现在可以通过@H_301_12@CSS3@H_301_12@或者是@H_301_12@JS@H_301_12@来实现,如果是通过@H_301_12@JS@H_301_12@来实现的话,需要其他@H_301_12@JS@H_301_12@库(比如@H_301_12@JQuery@H_301_12@)来支持,实际上底层实现还是靠其他@H_301_12@JS@H_301_12@库,只是@H_301_12@NG@H_301_12@将其封装了,@H_301_12@
使其更易使用。
- enter:元素添加到DOM@H_301_12@中时执行动画;@H_301_12@
- leave:元素从DOM@H_301_12@删除时执行动画;@H_301_12@
- move:移动元素时执行动画;
- beforeAddClass:在给元素添加CLASS@H_301_12@之前执行动画;@H_301_12@
- addClass:在给元素添加CLASS@H_301_12@时执行动画;@H_301_12@
- beforeRemoveClass:在给元素删除CLASS@H_301_12@之前执行动画;@H_301_12@
- removeClass:在给元素删除CLASS@H_301_12@时执行动画。@H_301_12@
其相关参数为:
varngModule=angular.module('YourApp',['ngAnimate']);
demoApp.animation('.my-crazy-animation',sans-serif; font-size:16px"> enter:function(element,done){
//runtheanimationhereandcalldonewhentheanimationiscomplete
returnfunction(cancelled){
//this(optional)functionwillbecalledwhentheanimation
//completesorwhentheanimationiscancelled(thecancelled
//flagwillbesettotrueifcancelled).
leave:function(element,done){},sans-serif; font-size:16px"> move:function(element,sans-serif; font-size:16px"> //animationthatcanbetriggeredbeforetheclassisadded
beforeAddClass:function(element,className,sans-serif; font-size:16px"> //animationthatcanbetriggeredaftertheclassisadded
addClass:function(element,sans-serif; font-size:16px"> //animationthatcanbetriggeredbeforetheclassisremoved
beforeRemoveClass:function(element,sans-serif; font-size:16px"> //animationthatcanbetriggeredaftertheclassisremoved
removeClass:function(element,done){}
});
4.10.2动画效果示例@H_301_12@
下面我们来看下DEMO@H_301_12@中的例子(例@H_301_12@10@H_301_12@)。@H_301_12@
1.首先,我们在@H_301_12@demoApp@H_301_12@下定义一个动画效果,匹配@H_301_12@CLASS@H_301_12@:@H_301_12@”.border-animation”
demoApp.animation('.border-animation',sans-serif; font-size:16px"> $(element).stop().animate({
'border-width':1
done();
'border-width':50
动画效果的含义就是:在匹配CLASS@H_301_12@为@H_301_12@border-animation的元素添加一个CLASS@H_301_12@之前使其边框的宽度在@H_301_12@2@H_301_12@秒内变为@H_301_12@1PX@H_301_12@;并在其移除一个@H_301_12@CLASS@H_301_12@时使其边框的宽度在@H_301_12@3@H_301_12@秒内变为@H_301_12@50PX@H_301_12@。@H_301_12@
<divclass="border-animation"ng-show="testShow"></div>
ng-show为false@H_301_12@时会为其加上“@H_301_12@ng-hide@H_301_12@“的@H_301_12@CLASS;@H_301_12@ng-show为true@H_301_12@时会为其移除“@H_301_12@ng-hide@H_301_12@“的@H_301_12@CLASS@H_301_12@,从而触发动画效果。@H_301_12@
demoApp.controller("test10Controller",$animate){
$scope.testShow=true;
});
5功能演示
略(详情请看AngularJS/demoWEB演示)@H_301_12@
6AngularJS进阶
6.1数据绑定原理研究
Angular用户都想知道数据绑定是怎么实现的。你可能会看到各种各样的词汇:@H_301_12@$watch@H_301_12@、@H_301_12@$apply@H_301_12@、@H_301_12@$digest@H_301_12@、@H_301_12@dirty-checking...@H_301_12@它们是什么?它们是如何工作的呢?这里我想回答这些问题,其实它们在官方的文档里都已经回答了,但是我还是想把它们结合在一起来讲,但是我只是用一种简单的方法来讲解,如果要想了解技术细节,查看源代码。@H_301_12@
6.1.1AngularJS扩展事件循环
我们的浏览器一直在等待事件,比如用户交互。假如你点击一个按钮或者在输入框里输入东西,事件的回调函数就会在javascript@H_301_12@解释器里执行,然后你就可以做任何@H_301_12@DOM@H_301_12@操作,等回调函数执行完毕时,浏览器就会相应地对@H_301_12@DOM@H_301_12@做出变化。(记住,这是个重要的概念),为了解释什么是@H_301_12@context@H_301_12@以及它如何工作,我们还需要解释更多的概念。@H_301_12@
6.1.2$watch队列@H_301_12@
每次你绑定一些东西到你的DOM@H_301_12@上时你就会往@H_301_12@$watch@H_301_12@队列里插入一条@H_301_12@$watch@H_301_12@。想象一下@H_301_12@$watch@H_301_12@就是那个可以检测它监视的@H_301_12@model@H_301_12@里时候有变化的东西。例如你有如下的代码:@H_301_12@
/*Viewindex.html*/
User:<inputtype="text"ng-model="user"/>
Password:<inputtype="password"ng-model="pass"/>
在这里我们有个$scope.user@H_301_12@,他被绑定在了第一个输入框上,还有个@H_301_12@$scope.pass@H_301_12@,它被绑定在了第二个输入框上,然后我们在@H_301_12@$watchlist@H_301_12@里面加入两个@H_301_12@$watch@H_301_12@。@H_301_12@
再看下面的例子:
/*Controllercontrollers.js*/
app.controller('MainCtrl',sans-serif; font-size:16px"> $scope.foo="Foo";
$scope.world="World";
这里,即便我们在$scope@H_301_12@上添加了两个东西,但是只有一个绑定在了@H_301_12@DOM@H_301_12@上,因此在这里只生成了一个@H_301_12@$watch@H_301_12@。@H_301_12@
$scope.people=[...];
<ling-repeat="personinpeople">
{{person.name}}-{{person.age}}
这里又生成了多少个$watch@H_301_12@呢?每个@H_301_12@person@H_301_12@有两个(一个@H_301_12@name@H_301_12@,一个@H_301_12@age@H_301_12@),然后@H_301_12@ng-repeat@H_301_12@又有一个,因此@H_301_12@10@H_301_12@个@H_301_12@person@H_301_12@一共是@H_301_12@(2*10)+1,@H_301_12@也就是说有@H_301_12@21@H_301_12@个@H_301_12@$watch@H_301_12@。@H_301_12@
因此,每一个绑定到了DOM@H_301_12@上的数据都会生成一个@H_301_12@$watch@H_301_12@。@H_301_12@
那这写$watch@H_301_12@是什么时候生成的呢?@H_301_12@
当我们的模版加载完毕时,也就是在linking@H_301_12@阶段(@H_301_12@Angular@H_301_12@分为@H_301_12@compile@H_301_12@阶段和@H_301_12@linking@H_301_12@阶段),@H_301_12@Angular@H_301_12@解释器会寻找每个@H_301_12@directive@H_301_12@,然后生成每个需要的@H_301_12@$watch@H_301_12@。@H_301_12@
6.1.3$digest循环@H_301_12@
还记得我前面提到的扩展的事件循环吗?当浏览器接收到可以被angularcontext@H_301_12@处理的事件时,@H_301_12@$digest@H_301_12@循环就会触发。这个循环是由两个更小的循环组合起来的。一个处理@H_301_12@evalAsync@H_301_12@队列,另一个处理@H_301_12@$watch@H_301_12@队列。这个是处理什么的呢?@H_301_12@$digest@H_301_12@将会遍历我们的@H_301_12@$watch@H_301_12@,然后询问:@H_301_12@
•嘿,$watch@H_301_12@,你的值是什么?@H_301_12@
◦是9。
•好的,它改变过吗?
◦没有,先生。
•(这个变量没变过,那下一个)
•你呢,你的值是多少?
◦报告,是Foo。
•刚才改变过没?
◦改变过,刚才是Bar。
•(很好,我们有DOM@H_301_12@需要更新了)@H_301_12@
•继续询问直到$watch@H_301_12@队列都检查过。@H_301_12@
这就是所谓的dirty-checking@H_301_12@。既然所有的@H_301_12@$watch@H_301_12@都检查完了,那就要问了:有没有@H_301_12@$watch@H_301_12@更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的@H_301_12@$watch@H_301_12@都没有变化。这样就能够保证每个@H_301_12@model@H_301_12@都已经不会再变化。记住如果循环超过@H_301_12@10@H_301_12@次的话,它将会抛出一个异常,防止无限循环。当@H_301_12@$digest@H_301_12@循环结束时,@H_301_12@DOM@H_301_12@相应地变化。@H_301_12@
例如:
$scope.name="Foo";
$scope.changeFoo=function(){
$scope.name="Bar";
{{name}}
<buttonng-click="changeFoo()">Changethename</button>
这里我们有一个$watch@H_301_12@因为@H_301_12@ng-click@H_301_12@不生成@H_301_12@$watch@H_301_12@(函数是不会变的)。@H_301_12@
我们可以看出ng@H_301_12@的处理流程:@H_301_12@
•我们按下按钮;
•浏览器接收到一个事件,进入angularcontext@H_301_12@;@H_301_12@
•$digest@H_301_12@循环开始执行,查询每个@H_301_12@$watch@H_301_12@是否变化;@H_301_12@
•由于监视$scope.name@H_301_12@的@H_301_12@$watch@H_301_12@报告了变化,它会强制再执行一次@H_301_12@$digest@H_301_12@循环;@H_301_12@
•新的$digest@H_301_12@循环没有检测到变化;@H_301_12@
•浏览器拿回控制权,更新与$scope.name@H_301_12@新值相应部分的@H_301_12@DOM@H_301_12@。@H_301_12@
这里很重要的是每一个进入angularcontext@H_301_12@的事件都会执行一个@H_301_12@$digest@H_301_12@循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有@H_301_12@$watch@H_301_12@。@H_301_12@
6.1.4如何进入@H_301_12@angularcontext@H_301_12@
谁决定什么事件进入angularcontext@H_301_12@,而哪些又不进入呢?通过@H_301_12@$apply@H_301_12@!@H_301_12@
如果当事件触发时,你调用$apply@H_301_12@,它会进入@H_301_12@angularcontext@H_301_12@,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用@H_301_12@$apply@H_301_12@啊,为什么?@H_301_12@Angular@H_301_12@已经做了!因此你点击带有@H_301_12@ng-click@H_301_12@的元素时,时间就会被封装到一个@H_301_12@$apply@H_301_12@调用。如果你有一个@H_301_12@ng-model="foo"@H_301_12@的输入框,然后你敲一个@H_301_12@f@H_301_12@,事件就会这样调用@H_301_12@$apply("foo='f';")@H_301_12@。@H_301_12@
Angular什么时候不会自动为我们@H_301_12@$apply@H_301_12@呢?@H_301_12@
这是Angular@H_301_12@新手共同的痛处。为什么我的@H_301_12@jQuery@H_301_12@不会更新我绑定的东西呢?因为@H_301_12@jQuery@H_301_12@没有调用@H_301_12@$apply@H_301_12@,事件没有进入@H_301_12@angularcontext@H_301_12@,@H_301_12@$digest@H_301_12@循环永远没有执行。@H_301_12@
我们来看一个有趣的例子:
假设我们有下面这个directive@H_301_12@和@H_301_12@controller@H_301_12@。@H_301_12@
/*Controllerapp.js*/
app.directive('clickable',sans-serif; font-size:16px"> restrict:"E",sans-serif; font-size:16px"> foo:'=',sans-serif; font-size:16px"> bar:'='
template:'<ulstyle="<li>{{foo}}</li><li>{{bar}}</li></ul>',sans-serif; font-size:16px"> element.bind('click',sans-serif; font-size:16px"> scope.foo++;
scope.bar++;
$scope.foo=0;
$scope.bar=0;
它将foo@H_301_12@和@H_301_12@bar@H_301_12@从@H_301_12@controller@H_301_12@里绑定到一个@H_301_12@list@H_301_12@里面,每次点击这个元素的时候,@H_301_12@foo@H_301_12@和@H_301_12@bar@H_301_12@都会自增@H_301_12@1@H_301_12@。那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到@H_301_12@$apply@H_301_12@里面的常见的事件,这意味着我们会失去我们的计数吗?不会。@H_301_12@
真正的结果是:$scope@H_301_12@确实改变了,但是没有强制@H_301_12@$digest@H_301_12@循环,监视@H_301_12@foo@H_301_12@和@H_301_12@bar@H_301_12@的@H_301_12@$watch@H_301_12@没有执行。也就是说如果我们自己执行一次@H_301_12@$apply@H_301_12@那么这些@H_301_12@$watch@H_301_12@就会看见这些变化,然后根据需要更新@H_301_12@DOM@H_301_12@。@H_301_12@
scope.$apply();
$apply是我们的@H_301_12@$scope@H_301_12@(或者是@H_301_12@direcvie@H_301_12@里的@H_301_12@link@H_301_12@函数中的@H_301_12@scope@H_301_12@)的一个函数,调用它会强制一次@H_301_12@$digest@H_301_12@循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行@H_301_12@$apply@H_301_12@的标志)。@H_301_12@
更好的使用$apply@H_301_12@的方法:@H_301_12@
scope.$apply(function(){
有什么不一样的?差别就是在第一个版本中,我们是在angularcontext@H_301_12@的外面更新的数据,如果有发生错误,@H_301_12@Angular@H_301_12@永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个@H_301_12@alert@H_301_12@框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进@H_301_12@$apply@H_301_12@里面,@H_301_12@Angular@H_301_12@永远不会知道失败了,@H_301_12@alert@H_301_12@框就永远不会弹出来了。@H_301_12@
因此,如果你想使用一个jQuery@H_301_12@插件,并且要执行@H_301_12@$digest@H_301_12@循环来更新你的@H_301_12@DOM@H_301_12@的话,要确保你调用了@H_301_12@$apply@H_301_12@。@H_301_12@
有时候我想多说一句的是有些人在不得不调用$apply@H_301_12@时会“感觉不妙”,因为他们会觉得他们做错了什么。其实不是这样的,@H_301_12@Angular@H_301_12@不是什么魔术师,他也不知道第三方库想要更新绑定的数据。@H_301_12@
6.1.5使用@H_301_12@$watch@H_301_12@来监视@H_301_12@
你已经知道了我们设置的任何绑定都有一个它自己的$watch@H_301_12@,当需要时更新@H_301_12@DOM@H_301_12@,但是我们如果要自定义自己的@H_301_12@watches@H_301_12@呢?简单,来看个例子:@H_301_12@
$scope.name="Angular";
$scope.updated=-1;
$scope.$watch('name',sans-serif; font-size:16px"> $scope.updated++;
<bodyng-controller="MainCtrl">
<inputng-model="name"/>
Nameupdated:{{updated}}times.
</body>
这就是我们创造一个新的$watch@H_301_12@的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,在这里,@H_301_12@$scope.name(@H_301_12@注意我们只需要@H_301_12@
用name)@H_301_12@。第二个参数是当@H_301_12@$watch@H_301_12@说我监视的表达式发生变化后要执行的。我们要知道的第一件事就是当@H_301_12@controller@H_301_12@执行到这个@H_301_12@$watch@H_301_12@时,它会立即执行一次,因此我们设置@H_301_12@updated@H_301_12@为@H_301_12@-1@H_301_12@。@H_301_12@
$scope.updated=0;
if(newValue===oldValue){return;}//AKAfirstrun
watch的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。@H_301_12@
$scope.user={name:"Fox"};
$scope.$watch('user',sans-serif; font-size:16px"> if(newValue===oldValue){return;}
<inputng-model="user.name"/>
我们想要监视$scope.user@H_301_12@对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。@H_301_12@
呃?没用,为啥?因为$watch@H_301_12@默认是比较两个对象所引用的是否相同,在例子@H_301_12@1@H_301_12@和@H_301_12@2@H_301_12@里面,每次更改@H_301_12@$scope.name@H_301_12@都会创建一个新的基本变量,因此@H_301_12@$watch@H_301_12@会执行,因为对这个变量的引用已经改变了。在上面的例子里,我们在监视@H_301_12@$scope.user@H_301_12@,当我们改变@H_301_12@$scope.user.name@H_301_12@时,对@H_301_12@$scope.user@H_301_12@的引用是不会改变的,我们只是每次创建了一个新的@H_301_12@$scope.user.name@H_301_12@,但是@H_301_12@$scope.user@H_301_12@永远是一样的。@H_301_12@
现在有用了吧!因为我们对$watch@H_301_12@加入了第三个参数,它是一个@H_301_12@bool@H_301_12@类型的参数,表示的是我们比较的是对象的值而不是引用。由于当我们更新@H_301_12@$scope.user.name@H_301_12@时@H_301_12@$scope.user@H_301_12@也会改变,所以能够正确触发。@H_301_12@
6.1.6总结
我希望你们已经学会了在Angular@H_301_12@中数据绑定是如何工作的。我猜想你的第一印象是@H_301_12@dirty-checking@H_301_12@很慢,好吧,其实是不对的。它像闪电般快。但是,如果你在一个模版里有@H_301_12@2000-3000@H_301_12@个@H_301_12@watch@H_301_12@,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了。@H_301_12@
无论如何,随着ECMAScript6@H_301_12@的到来,在@H_301_12@Angular@H_301_12@未来的版本里我们将会有@H_301_12@Object.observe@H_301_12@那样会极大改善@H_301_12@$digest@H_301_12@循环的速度。@H_301_12@
6.2自定义指令详解@H_301_12@
angular的指令机制。@H_301_12@angular@H_301_12@通过指令的方式实现了@H_301_12@HTML@H_301_12@的扩展,增强后的@H_301_12@HTML@H_301_12@不仅长相焕然一新,同时也获得了很多强大的技能。更厉害的是,你还可以自定义指令,这就意味着@H_301_12@HTML@H_301_12@标签的范围可以扩展到无穷大。@H_301_12@angular@H_301_12@赋予了你造物主的能力。既然是作为@H_301_12@angular@H_301_12@的精华之一,相应的指令相关的知识也很多的。@H_301_12@
6.2.1指令的编译过程
在开始自定义指令之前,我们有必要了解一下指令在框架中的执行流程:
1.浏览器得到HTML@H_301_12@字符串内容,解析得到@H_301_12@DOM@H_301_12@结构。@H_301_12@
2.ng引入,把@H_301_12@DOM@H_301_12@结构扔给@H_301_12@$compile@H_301_12@函数处理:@H_301_12@
①找出DOM@H_301_12@结构中有变量占位符;@H_301_12@
②匹配找出DOM@H_301_12@中包含的所有指令引用;@H_301_12@
③把指令关联到DOM@H_301_12@;@H_301_12@
④关联到DOM@H_301_12@的多个指令按权重排列;@H_301_12@
⑤执行指令中的compile@H_301_12@函数(改变@H_301_12@DOM@H_301_12@结构,返回@H_301_12@link@H_301_12@函数);@H_301_12@
⑥得到的所有link@H_301_12@函数组成一个列表作为@H_301_12@$compile@H_301_12@函数的返回。@H_301_12@
3.执行@H_301_12@link@H_301_12@函数(连接模板的@H_301_12@scope@H_301_12@)。@H_301_12@
这里注意区别一下$compile@H_301_12@和@H_301_12@compile@H_301_12@,前者是@H_301_12@ng@H_301_12@内部的编译服务,后者是指令中的编译函数,两者发挥作用的范围不同。@H_301_12@compile@H_301_12@和@H_301_12@link@H_301_12@函数息息相关又有所区别,这个在后面会讲。了解执行流程对后面的理解会有帮助。@H_301_12@
在这里有些人可能会问,angular@H_301_12@不就是一个@H_301_12@js@H_301_12@框架吗,怎么还能跟编译扯上呢,又不是像@H_301_12@C++@H_301_12@那样的高级语言。其实此编译非彼编译,@H_301_12@ng@H_301_12@编译的工作是解析指令、绑定监听器、替换模板中的变量等。因为工作方式很像高级语言编辑中的递归、堆栈过程,所以起名为编译,不要疑惑。@H_301_12@
6.2.2指令的使用方式及命名方法@H_301_12@
指令的几种使用方式如下:
- 作为标签:<my-dir></my-dir>@H_301_12@
- 作为属性:<spanmy-dir="exp"></span>@H_301_12@
- 作为注释:<!--directive:my-direxp-->@H_301_12@
- 作为类名:<spanclass="my-dir:exp;"></span>@H_301_12@
其实常用的就是作为标签和属性,下面两种用法目前还没见过,感觉就是用来卖萌的,姑且留个印象。我们自定义的指令就是要支持这样的用法。
关于自定义指令的命名,你可以随便怎么起名字都行,官方是推荐用[@H_301_12@命名空间@H_301_12@-@H_301_12@指令名称@H_301_12@]@H_301_12@这样的方式,像@H_301_12@ng-controller@H_301_12@。不过你可千万不要用@H_301_12@ng-@H_301_12@前缀了,防止与系统自带的指令重名。另外一个需知道的地方,指令命名时用驼峰规则,使用时用@H_301_12@-@H_301_12@分割各单词。如:定义@H_301_12@myDirective@H_301_12@,使用时像这样:@H_301_12@<my-directive>@H_301_12@。@H_301_12@
6.2.3自定义指令的配置参数@H_301_12@
下面是定义一个标准指令的示例,可配置的参数包括以下部分:
myModule.directive('namespaceDirectiveName',functionfactory(injectables){
vardirectiveDefinitionObject={
restrict:string,//指令的使用方式,包括标签,属性,类,注释@H_301_12@
priority:number,//指令执行的优先级@H_301_12@
template:string,//指令使用的模板,用@H_301_12@HTML@H_301_12@字符串的形式表示@H_301_12@
templateUrl:string,//从指定的@H_301_12@url@H_301_12@地址加载模板@H_301_12@
replace:bool,//是否用模板替换当前元素,若为@H_301_12@false@H_301_12@,则@H_301_12@append@H_301_12@在当前元素上@H_301_12@
transclude:bool,//是否将当前元素的内容转移到模板中@H_301_12@
scope:boolorobject,//指定指令的作用域@H_301_12@
controller:functioncontrollerConstructor($scope,$element,$transclude){...},//定义与其他指令进行交互的接口函数@H_301_12@
require:string,//指定需要依赖的其他指令@H_301_12@
link:functionpostLink(scope,iElement,iAttrs){...},//以编程的方式操作@H_301_12@DOM@H_301_12@,包@H_301_12@
括添加监听器等
compile:functioncompile(tElement,tAttrs,transclude){
return:{
pre:functionpreLink(scope,iAttrs,controller){...},sans-serif; font-size:16px"> post:functionpostLink(scope,controller){...}
}//编程的方式修改@H_301_12@DOM@H_301_12@模板的副本,可以返回链接函数@H_301_12@
returndirectiveDefinitionObject;
看上去好复杂的样子,定义一个指令需要这么多步骤嘛?当然不是,你可以根据自己的需要来选择使用哪些参数。事实上priority@H_301_12@和@H_301_12@compile@H_301_12@用的比较少,@H_301_12@template@H_301_12@和@H_301_12@templateUrl@H_301_12@又是互斥的,两者选其一即可。所以不必紧张,接下来分别学习一下这些参数:@H_301_12@
l指令的表现配置参数:restrict@H_301_12@、@H_301_12@template@H_301_12@、@H_301_12@templateUrl@H_301_12@、@H_301_12@replace@H_301_12@、@H_301_12@transclude@H_301_12@;@H_301_12@
l指令的行为配置参数:compile@H_301_12@和@H_301_12@link@H_301_12@;@H_301_12@
l指令划分作用域配置参数:scope@H_301_12@;@H_301_12@
l指令间通信配置参数:controller@H_301_12@和@H_301_12@require@H_301_12@。@H_301_12@
6.2.3指令的表现参数restrict@H_301_12@等@H_301_12@
指令的表现配置参数:restrict@H_301_12@、@H_301_12@template@H_301_12@、@H_301_12@templateUrl@H_301_12@、@H_301_12@replace@H_301_12@、@H_301_12@transclude@H_301_12@。@H_301_12@
我将先从一个简单的例子开始。
例子的代码如下:
app.directive('sayHello',sans-serif; font-size:16px"> restrict:'E',sans-serif; font-size:16px"> template:'<div>hello</div>'
然后在页面中,我们就可以使用这个名为sayHello@H_301_12@的指令了,它的作用就是输出一个@H_301_12@hello@H_301_12@单词。像这样使用:@H_301_12@
<say-hello></say-hello>
这样页面就会显示出hello@H_301_12@了,看一下生成的代码:@H_301_12@
<say-hello>
<div>hello</div>
</say-hello>
稍稍解释一下我们用到的两个参数,restirct@H_301_12@用来指定指令的使用类型,其取值及含义如下:@H_301_12@
取值 |
含义 |
使用示例 |
E |
<my-menutitle=Products></my-menu> |
|
A |
<divmy-menu=Products></div> |
|
C |
类 |
<divclass="my-menu":Products></div> |
M |
注释 |
<!--directive:my-menuProducts--> |
默认值是A@H_301_12@。也可以使用这些值的组合,如@H_301_12@EA@H_301_12@,@H_301_12@EC@H_301_12@等等。我们这里指定为@H_301_12@E@H_301_12@,那么它就可以像标签一样使用了。如果指定为@H_301_12@A@H_301_12@,我们使用起来应该像这样:@H_301_12@
<divsay-hello></div>
从生成的代码中,你也看到了template@H_301_12@的作用,它就是描述你的指令长什么样子,这部分内容将出现在页面中,即该指令所在的模板中,既然是模板中,@H_301_12@template@H_301_12@的内容中也可以使用@H_301_12@ng-modle@H_301_12@等其他指令,就像在模板中使用一样。@H_301_12@
在上面生成的代码中,我们看到了<div>hello</div>@H_301_12@外面还包着一层@H_301_12@<say-hello>@H_301_12@标签,如果我们不想要这一层多余的东西了,@H_301_12@replace@H_301_12@就派上用场了,在配置中将@H_301_12@replace@H_301_12@赋值为@H_301_12@true@H_301_12@,将得到如下结构:@H_301_12@
replace@H_301_12@的作用正如其名,将指令标签替换为了@H_301_12@temple@H_301_12@中定义的内容。不写的话默认为@H_301_12@false@H_301_12@。@H_301_12@
上面的template@H_301_12@未免也太简单了,如果你的模板@H_301_12@HTML@H_301_12@较复杂,如自定义一个@H_301_12@ui@H_301_12@组件指令,难道要拼接老长的字符串?当然不需要,此时只需用@H_301_12@templateUrl@H_301_12@便可解决问题。你可以将指令的模板单独命名为一个@H_301_12@html@H_301_12@文件,然后在指令定义中使用@H_301_12@templateUrl@H_301_12@指定好文件的路径即可,如:@H_301_12@
templateUrl:‘@H_301_12@helloTemplate.html@H_301_12@’@H_301_12@
系统会自动发一个http@H_301_12@请求来获取到对应的模板内容。是不是很方便呢,你不用纠结于拼接字符串的烦恼了。如果你是一个追求完美的有考虑性能的工程师,可能会发问:那这样的话岂不是要牺牲一个@H_301_12@http@H_301_12@请求?这也不用担心,因为@H_301_12@ng@H_301_12@的模板还可以用另外一种方式定义,那就是使用@H_301_12@<script>@H_301_12@标签。使用起来如下:@H_301_12@
<scripttype="text/ng-template"id="helloTemplate.html">
你可以把这段代码写在页面头部,这样就不必去请求它了。在实际项目中,你也可以将所有的模板内容集中在一个文件中,只加载一次,然后根据id@H_301_12@来取用。@H_301_12@
接下来我们来看另一个比较有用的配置:transclude@H_301_12@,定义是否将当前元素的内容转移到模板中。看解释有点抽象,不过亲手试试就很清楚了,看下面的代码(例@H_301_12@06@H_301_12@):@H_301_12@
template:'<div>hello,@H_301_12@<bng-transclude></b>@H_301_12@!@H_301_12@</div>',sans-serif; font-size:16px"> replace:true,sans-serif; font-size:16px"> transclude:true
指定了transclude@H_301_12@为@H_301_12@true@H_301_12@,并且@H_301_12@template@H_301_12@修改了一下,加了一个@H_301_12@<b>@H_301_12@标签,并在上面使用了@H_301_12@ng-transclude@H_301_12@指令,用来告诉指令把内容转移到的位置。那我们要转移的内容是什么呢?请看使用指令时的变化:@H_301_12@
<say-hello>美女@H_301_12@</say-hello>@H_301_12@
内容是什么你也看到了哈~@H_301_12@在运行的时候,美女将会被转移到@H_301_12@<b>@H_301_12@标签中,原来此配置的作用就是——乾坤大挪移!看效果:@H_301_12@
hello,美女!@H_301_12@
这个还是很有用的,因为你定义的指令不可能老是那么简单,只有一个空标签。当你需要对指令中的内容进行处理时,此参数便大有可用。
6.2.4指令的行为参数:@H_301_12@compile@H_301_12@和@H_301_12@link@H_301_12@
6.2.3中简单介绍了自定义一个指令的几个简单参数,@H_301_12@restrict@H_301_12@、@H_301_12@template@H_301_12@、@H_301_12@templateUrl@H_301_12@、@H_301_12@replace@H_301_12@、@H_301_12@transclude@H_301_12@,这几个理解起来相对容易很多,因为它们只涉及到了表现,而没有涉及行为。我们继续学习@H_301_12@ng@H_301_12@自定义指令的几个重量级参数:@H_301_12@compile@H_301_12@和@H_301_12@link@H_301_12@
l理解compile@H_301_12@和@H_301_12@link@H_301_12@
不知大家有没有这样的感觉,自己定义指令的时候跟写jQuery@H_301_12@插件有几分相似之处,都是先预先定义好页面结构及监听函数,然后在某个元素上调用一下,该元素便拥有了特殊的功能。区别在于,@H_301_12@jQuery@H_301_12@的侧重点是@H_301_12@DOM@H_301_12@操作,而@H_301_12@ng@H_301_12@的指令中除了可以进行@H_301_12@DOM@H_301_12@操作外,更注重的是数据和模板的绑定。@H_301_12@jQuery@H_301_12@插件在调用的时候才开始初始化,而@H_301_12@ng@H_301_12@指令在页面加载进来的时候就被编译服务@H_301_12@($compile)@H_301_12@初始化好了。@H_301_12@
在指令定义对象中,有compile@H_301_12@和@H_301_12@link@H_301_12@两个参数,它们是做什么的呢?从字面意义上看,编译、链接,貌似太抽象了点。其实可大有内涵,为了在自定义指令的时候能正确使用它们,现在有必要了解一下@H_301_12@ng@H_301_12@是如何编译指令的。@H_301_12@
l指令的解析流程详解
我们知道ng@H_301_12@框架会在页面载入完毕的时候,根据@H_301_12@ng-app@H_301_12@划定的作用域来调用@H_301_12@$compile@H_301_12@服务进行编译,这个@H_301_12@$compile@H_301_12@就像一个大总管一样,清点作用域内的@H_301_12@DOM@H_301_12@元素,看看哪些元素上使用了指令@H_301_12@(@H_301_12@如@H_301_12@<divng-modle=@H_301_12@”@H_301_12@m@H_301_12@”@H_301_12@></div>)@H_301_12@,或者哪些元素本身就是个指令@H_301_12@(@H_301_12@如@H_301_12@<mydierc></mydirec>)@H_301_12@,或者使用了插值指令@H_301_12@({{}}@H_301_12@也是一种指令,叫@H_301_12@interpolationdirective)@H_301_12@,@H_301_12@$compile@H_301_12@大总管会把清点好的财产做一个清单,然后根据这些指令的优先级@H_301_12@(priority)@H_301_12@排列一下,真是个细心的大总管哈@H_301_12@~@H_301_12@大总管还会根据指令中的配置参数@H_301_12@(template@H_301_12@,@H_301_12@place@H_301_12@,@H_301_12@transclude@H_301_12@等@H_301_12@)@H_301_12@转换@H_301_12@DOM@H_301_12@,让指令“初具人形”。@H_301_12@
然后就开始按顺序执行各指令的compile@H_301_12@函数,注意此处的@H_301_12@compile@H_301_12@可不是大总管@H_301_12@$compile@H_301_12@,人家带着@H_301_12@$@H_301_12@是土豪,此处执行的@H_301_12@compile@H_301_12@函数是我们指令中配置的,@H_301_12@compile@H_301_12@函数中可以访问到@H_301_12@DOM@H_301_12@节点并进行操作,其主要职责就是进行@H_301_12@DOM@H_301_12@转换,每个@H_301_12@compile@H_301_12@函数执行完后都会返回一个@H_301_12@link@H_301_12@函数,这些@H_301_12@link@H_301_12@函数会被大总管汇合一下组合成一个合体后的@H_301_12@link@H_301_12@函数,为了好理解,我们可以把它想象成葫芦小金刚,就像是进行了这样的处理。@H_301_12@
//合体后的@H_301_12@link@H_301_12@函数@H_301_12@
functionAB(){
A();//子@H_301_12@link@H_301_12@函数@H_301_12@
B();//子@H_301_12@link@H_301_12@函数@H_301_12@
接下来进入link@H_301_12@阶段,合体后的@H_301_12@link@H_301_12@函数被执行。所谓的链接,就是把@H_301_12@view@H_301_12@和@H_301_12@scope@H_301_12@链接起来。链接成啥样呢?就是我们熟悉的数据绑定,通过在@H_301_12@DOM@H_301_12@上注册监听器来动态修改@H_301_12@scope@H_301_12@中的数据,或者是使用@H_301_12@$watchs@H_301_12@监听@H_301_12@scope@H_301_12@中的变量来修改@H_301_12@DOM@H_301_12@,从而建立双向绑定。由此也可以断定,葫芦小金刚可以访问到@H_301_12@scope@H_301_12@和@H_301_12@DOM@H_301_12@节点。@H_301_12@
不要忘了我们在定义指令中还配置着一个link@H_301_12@参数呢,这么多@H_301_12@link@H_301_12@千万别搞混了。那这@H_301_12@
个link@H_301_12@函数是干嘛的呢,我们不是有葫芦小金刚了嘛?那我告诉你,其实它是一个小三。此话怎讲?@H_301_12@compile@H_301_12@函数执行后返回@H_301_12@link@H_301_12@函数,但若没有配置@H_301_12@compile@H_301_12@函数呢?葫芦小金刚自然就不存在了。@H_301_12@
正房不在了,当然就轮到小三出马了,大总管$compile@H_301_12@就把这里的@H_301_12@link@H_301_12@函数拿来执行。这就意味着,配置的@H_301_12@link@H_301_12@函数也可以访问到@H_301_12@scope@H_301_12@以及@H_301_12@DOM@H_301_12@节点。值得注意的是,@H_301_12@compile@H_301_12@函数通常是不会被配置的,因为我们定义一个指令的时候,大部分情况不会通过编程的方式进行@H_301_12@DOM@H_301_12@操作,而更多的是进行监听器的注册、数据的绑定。所以,小三名正言顺的被大总管宠爱。@H_301_12@
听完了大总管、葫芦小金刚和小三的故事,你是不是对指令的解析过程比较清晰了呢?不过细细推敲,你可能还是会觉得情节生硬,有些细节似乎还是没有透彻的明白,所以还需要再理解下面的知识点:
lcompile和@H_301_12@link@H_301_12@的区别@H_301_12@
其实在我看完官方文档后就一直有疑问,为什么监听器、数据绑定不能放在compile@H_301_12@函数中,而偏偏要放在@H_301_12@link@H_301_12@函数中?为什么有了@H_301_12@compile@H_301_12@还需要@H_301_12@link@H_301_12@?就跟你质疑我编的故事一样,为什么最后小三被宠爱了?所以我们有必要探究一下,@H_301_12@compile@H_301_12@和@H_301_12@link@H_301_12@之间到底有什么区别。好,正房与小三的@H_301_12@PK@H_301_12@现在开始。@H_301_12@
首先是性能。举个例子:
<ling-repeat="ainarray">
<inputng-modle=”a.m”/>
我们的观察目标是ng-repeat@H_301_12@指令。假设一个前提是不存在@H_301_12@link@H_301_12@。大总管@H_301_12@$compile@H_301_12@在编译这段代码时,会查找到@H_301_12@ng-repeat@H_301_12@,然后执行它的@H_301_12@compile@H_301_12@函数,@H_301_12@compile@H_301_12@函数根据@H_301_12@array@H_301_12@的长度复制出@H_301_12@n@H_301_12@个@H_301_12@<li>@H_301_12@标签。而复制出的@H_301_12@<li>@H_301_12@节点中还有@H_301_12@<input>@H_301_12@节点并且使用了@H_301_12@ng-modle@H_301_12@指令,所以@H_301_12@compile@H_301_12@还要扫描它并匹配指令,然后绑定监听器。每次循环都做如此多的工作。而更加糟糕的一点是,我们会在程序中向@H_301_12@array@H_301_12@中添加元素,此时页面上会实时更新@H_301_12@DOM@H_301_12@,每次有新元素进来,@H_301_12@compile@H_301_12@函数都把上面的步骤再走一遍,岂不是要累死了,这样性能必然不行。@H_301_12@
现在扔掉那个假设,在编译的时候compile@H_301_12@就只管生成@H_301_12@DOM@H_301_12@的事,碰到需要绑定监听器的地方先存着,有几个存几个,最后把它们汇总成一个@H_301_12@link@H_301_12@函数,然后一并执行。这样就轻松多了,@H_301_12@compile@H_301_12@只需要执行一次,性能自然提升。@H_301_12@
另外一个区别是能力。
尽管compile@H_301_12@和@H_301_12@link@H_301_12@所做的事情差不多,但它们的能力范围还是不一样的。比如正房能管你的存款,小三就不能。小三能给你初恋的感觉,正房却不能。@H_301_12@
我们需要看一下compile@H_301_12@函数和@H_301_12@link@H_301_12@函数的定义:@H_301_12@
functioncompile(tElement,transclude){...}
functionlink(scope,sans-serif; font-size:16px"> 这些参数都是通过依赖注入而得到的,可以按需声明使用。从名字也容易看出,两个函数各自的职责是什么,compile@H_301_12@可以拿到@H_301_12@transclude@H_301_12@,允许你自己编程管理乾坤大挪移的行为。而@H_301_12@link@H_301_12@中可以拿到@H_301_12@scope@H_301_12@和@H_301_12@controller@H_301_12@,可以与@H_301_12@scope@H_301_12@进行数据绑定,与其他指令进行通信。两者虽然都可以拿到@H_301_12@element@H_301_12@,但是还是有区别的,看到各自的前缀了吧?@H_301_12@compile@H_301_12@拿到的是编译前的,是从@H_301_12@template@H_301_12@里拿过来的,而@H_301_12@link@H_301_12@拿到的是编译后的,已经与作用域建立了@H_301_12@
关联,这也正是link@H_301_12@中可以进行数据绑定的原因。@H_301_12@
我暂时只能理解到这个程度了。实在不想理解这些知识的话,只要简单记住一个原则就行了:如果指令只进行DOM@H_301_12@的修改,不进行数据绑定,那么配置在@H_301_12@compile@H_301_12@函数中,如果指令要进行数据绑定,那么配置在@H_301_12@link@H_301_12@函数中。@H_301_12@
6.2.5指令的划分作用域参数:@H_301_12@scope@H_301_12@
我们在上面写了一个简单的<say-hello></say-hello>@H_301_12@,能够跟美女打招呼。但是看看人家@H_301_12@ng@H_301_12@内置的指令,都是这么用的:@H_301_12@ng-model=@H_301_12@”@H_301_12@m@H_301_12@”,@H_301_12@ng-repeat=@H_301_12@”@H_301_12@ainarray@H_301_12@”,不单单是作为属性,还可以赋值给它,与作用域中的一个变量绑定好,内容就可以动态变化了。假如我们的@H_301_12@sayHello@H_301_12@可以这样用:@H_301_12@<say-hellospeak=@H_301_12@”@H_301_12@content@H_301_12@”@H_301_12@>@H_301_12@美女@H_301_12@</say-hello>@H_301_12@,把要对美女说的话写在一个变量@H_301_12@content@H_301_12@中,然后只要在@H_301_12@controller@H_301_12@中修改@H_301_12@content@H_301_12@的值,页面就可以显示对美女说的不同的话。这样就灵活多了,不至于见了美女只会说一句@H_301_12@hello@H_301_12@,然后就没有然后。@H_301_12@
为了实现这样的功能,我们需要使用scope@H_301_12@参数,下面来介绍一下。@H_301_12@
使用scope@H_301_12@为指令划分作用域@H_301_12@
顾名思义,scope@H_301_12@肯定是跟作用域有关的一个参数,它的作用是描述指令与父作用域的关系,这个父作用域是指什么呢?想象一下我们使用指令的场景,页面结构应该是这个样子:@H_301_12@
<divng-controller="testC">
<say-hellospeak="content">美女@H_301_12@</say-hello>@H_301_12@
</div> @H_301_12@
外层肯定会有一个controller@H_301_12@,而在@H_301_12@controller@H_301_12@的定义中大体是这个样子:@H_301_12@
$scope.content='今天天气真好!@H_301_12@';@H_301_12@
}); @H_301_12@
所谓sayHello@H_301_12@的父作用域就是这个名叫@H_301_12@testC@H_301_12@的控制器所管辖的范围,指令与父作用域的关系可以有如下取值:@H_301_12@
取值 |
说明 |
false |
默认值。使用父作用域作为自己的作用域 |
true |
新建一个作用域,该作用域继承父作用域 |
javascript对象@H_301_12@ |
与父作用域隔离,并指定可以从父作用域访问的变量 |
乍一看取值为false@H_301_12@和@H_301_12@true@H_301_12@好像没什么区别,因为取值为@H_301_12@true@H_301_12@时会继承父作用域,即父作用域中的任何变量都可以访问到,效果跟直接使用父作用域差不多。但细细一想还是有区别的,有了自己的作用域后就可以在里面定义自己的东西,与跟父作用域混在一起是有本质上的区别。好比是父亲的钱你想花多少花多少,可你自己挣的钱父亲能花多少就不好说了。你若想看这两个作用域的区别,可以在@H_301_12@link@H_301_12@函数中打印出来看看,还记得@H_301_12@link@H_301_12@函数中可以访问到@H_301_12@scope@H_301_12@吧。@H_301_12@
最有用的还是取值为第三种,一个对象,可以用键值来显式的指明要从父作用域中使用属性的方式。当scope@H_301_12@值为一个对象时,我们便建立了一个与父层隔离的作用域,不过也不是完全隔离,我们可以手工搭一座桥梁,并放行某些参数。我们要实现对美女说各种话就得靠这个。使用起来像这样:@H_301_12@
attributeName1:'BINDING_STRATEGY',sans-serif; font-size:16px"> attributeName2:'BINDING_STRATEGY',...
键为属性名称,值为绑定策略。等等!啥叫绑定策略?最讨厌冒新名词却不解释的行为!别急,听我慢慢道来。
先说属性名称吧,你是不是认为这个attributeName1@H_301_12@就是父作用域中的某个变量名称?错!其实这个属性名称是指令自己的模板中要使用的一个名称,并不对应父作用域中的变量,稍后的例子中我们来说明。再来看绑定策略,它的取值按照如下的规则:@H_301_12@
符号 |
说明 |
举例 |
@ |
传递一个字符串作为属性的值 |
str:‘@string’ |
= |
name:‘=username’ |
|
& |
getName:‘&getUserName’ |
总之就是用符号前缀来说明如何为指令传值。你肯定迫不及待要看例子了,我们结合例子看一下,小二,上栗子~@H_301_12@
举例说明
我想要实现上面想像的跟美女多说点话的功能,即我们给sayHello@H_301_12@指令加一个属性,通过给属性赋值来动态改变说话的内容主要代码如下:@H_301_12@
$scope.content='今天天气真好!@H_301_12@';@H_301_12@
template:'<div>hello,<bng-transclude></b>,{{cont}}</div>',sans-serif; font-size:16px"> cont:'=speak'
然后在模板中,我们如下使用指令:
看看运行效果:
美女今天天气真好!
执行的流程是这样的:
①指令被编译的时候会扫描到template@H_301_12@中的@H_301_12@{{cont}}@H_301_12@,发现是一个表达式;@H_301_12@
②查找scope@H_301_12@中的规则:通过@H_301_12@speak@H_301_12@与父作用域绑定,方式是传递父作用域中的属性;@H_301_12@
③speak@H_301_12@与父作用域中的@H_301_12@content@H_301_12@属性绑定,找到它的值“今天天气真好!”;@H_301_12@
④将content@H_301_12@的值显示在模板中。@H_301_12@
这样我们说话的内容content@H_301_12@就跟父作用域绑定到了一其,如果动态修改父作用域的@H_301_12@content@H_301_12@的值,页面上的内容就会跟着改变,正如你点击“换句话”所看到的一样。@H_301_12@
这个例子也太小儿科了吧!简单虽简单,但可以让我们理解清楚,为了检验你是不是真的明白了,可以思考一下如何修改指令定义,能让sayHello@H_301_12@以如下两种方式使用:@H_301_12@
<spansay-hellospeak="content">美女@H_301_12@</span>@H_301_12@
<spansay-hello="content">美女@H_301_12@</span>@H_301_12@
答案我就不说了,简单的很。下面有更重要的事情要做,我们说好了要写一个真正能用的东西来着。接下来就结合所学到的东西来写一个折叠菜单,即点击可展开,再点击一次就收缩回去的菜单。
控制器及指令的代码如下(例07@H_301_12@):@H_301_12@
$scope.text='大家好,我是一名前端工程师,我正在研究@H_301_12@AngularJs@H_301_12@,欢迎大家与我交流@H_301_12@';@H_301_12@
app.directive('expander',sans-serif; font-size:16px"> templateUrl:'expanderTemp.html',attris){
<scripttype="text/ng-template"id="expanderTemp.html">
<divclass="myBox">
<divclass="mytitle"ng-click="toggleText()">
<expanderetitle="title">{{text}}</expander>
还是比较容易看懂的,我只做一点必要的解释。首先我们定义模板的时候使用了ng@H_301_12@的一种定义方式@H_301_12@<scripttype=@H_301_12@”@H_301_12@text/ng-template@H_301_12@”@H_301_12@id="expanderTemp.html">@H_301_12@,在指令中就可以用@H_301_12@templateUrl@H_301_12@根据这个@H_301_12@id@H_301_12@来找到模板。指令中的@H_301_12@{{mytitle}}@H_301_12@表达式由@H_301_12@scope@H_301_12@参数指定从@H_301_12@etitle@H_301_12@传递,@H_301_12@etitle@H_301_12@指向了父作用域中的@H_301_12@title@H_301_12@。为了实现点击标题能够展开收缩内容,我们把这部分逻辑放在了@H_301_12@link@H_301_12@函数中,@H_301_12@link@H_301_12@函数可以访问到指令的作用域,我们定义@H_301_12@showText@H_301_12@属性来表示内容部分的显隐,定义@H_301_12@toggleText@H_301_12@函数来进行控制,然后在模板中绑定好。如果把@H_301_12@showText@H_301_12@和@H_301_12@toggleText@H_301_12@定义在@H_301_12@controller@H_301_12@中,作为@H_301_12@$scope@H_301_12@的属性呢?显然是不行的,这就是隔离作用域的意义所在,父作用域中的东西除了@H_301_12@title@H_301_12@之外通通被屏蔽。@H_301_12@
上面的例子中,scope@H_301_12@参数使用了@H_301_12@=@H_301_12@号来指定获取属性的类型为父作用域的属性,如果我们想在指令中使用父作用域中的函数,使用@H_301_12@&@H_301_12@符号即可,是同样的原理。@H_301_12@
6.2.6指令间通信参数:@H_301_12@controller@H_301_12@和@H_301_12@require@H_301_12@
使用指令来定义一个ui@H_301_12@组件是个不错的想法,首先使用起来方便,只需要一个标签或者属性就可以了,其次是可复用性高,通过@H_301_12@controller@H_301_12@可以动态控制@H_301_12@ui@H_301_12@组件的内容,而且拥有双向绑定的能力。当我们想做的组件稍微复杂一点,就不是一个指令可以搞定的了,就需要指令与指令的协作才可以完成,这就需要进行指令间通信。@H_301_12@
想一下我们进行模块化开发的时候的原理,一个模块暴露(exports@H_301_12@)对外的接口,另外一个模块引用(@H_301_12@require@H_301_12@)它,便可以使用它所提供的服务了。@H_301_12@ng@H_301_12@的指令间协作也是这个原理,这也正是自定义指令时@H_301_12@controller@H_301_12@参数和@H_301_12@require@H_301_12@参数的作用。@H_301_12@
controller参数用于定义指令对外提供的接口,它的写法如下:@H_301_12@
controller:functioncontrollerConstructor($scope,$transclude) @H_301_12@
它是一个构造器函数,将来可以构造出一个实例传给引用它的指令。为什么叫controller@H_301_12@(控制器)呢?其实就是告诉引用它的指令,你可以控制我。至于可以控制那些东西呢,就需要在函数体中进行定义了。先看@H_301_12@controller@H_301_12@可以使用的参数,作用域、节点、节点的属性、节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数。关于如何对外暴露接口,我们在下面的例子来说明。@H_301_12@
require参数便是用来指明需要依赖的其他指令,它的值是一个字符串,就是所依赖的指令的名字,这样框架就能按照你指定的名字来从对应的指令上面寻找定义好的@H_301_12@controller@H_301_12@了。不过还稍稍有点特别的地方,为了让框架寻找的时候更轻松些,我们可以在名字前面加个小小的前缀:@H_301_12@^@H_301_12@,表示从父节点上寻找,使用起来像这样:@H_301_12@require:@H_301_12@‘@H_301_12@^directiveName@H_301_12@’,如果不加,@H_301_12@$compile@H_301_12@服务只会从节点本身寻找。另外还可以使用前缀:?,此前缀将告诉@H_301_12@$compile@H_301_12@服务,如果所需的@H_301_12@controller@H_301_12@没找到,不要抛出异常。@H_301_12@
所需要了解的知识点就这些,接下来是例子时间,依旧是从书上抄来的一个例子,我们要做的是一个手风琴菜单,就是多个折叠菜单并列在一起,此例子用来展示指令间的通信再合适不过。
首先我们需要定义外层的一个结构,起名为accordion@H_301_12@,代码如下:@H_301_12@
app.directive('accordion',sans-serif; font-size:16px"> template:'<divng-transclude></div>',sans-serif; font-size:16px"> controller:function(){
varexpanders=[];
this.gotOpended=function(selectedExpander){
angular.forEach(expanders,function(e){
if(selectedExpander!=e){
e.showText=false;
this.addExpander=function(e){
expanders.push(e);
需要解释的只有controller@H_301_12@中的代码,我们定义了一个折叠菜单数组@H_301_12@expanders@H_301_12@,并且通过@H_301_12@this@H_301_12@关键字来对外暴露接口,提供两个方法。@H_301_12@gotOpended@H_301_12@接受一个@H_301_12@selectExpander@H_301_12@参数用来修改数组中对应@H_301_12@expander@H_301_12@的@H_301_12@showText@H_301_12@属性值,从而实现对各个子菜单的显隐控制。@H_301_12@addExpander@H_301_12@方法对外提供向@H_301_12@expanders@H_301_12@数组增加元素的接口,这样在子菜单的指令中,便可以调用它把自身加入到@H_301_12@accordion@H_301_12@中。@H_301_12@
看一下我们的expander@H_301_12@需要做怎样的修改呢:@H_301_12@
require:'^?accordion',sans-serif; font-size:16px"> title:'=etitle'
link:function(scope,attris,accordionController){
accordionController.addExpander(scope);
accordionController.gotOpended(scope);
首先使用require@H_301_12@参数引入所需的@H_301_12@accordion@H_301_12@指令,添加@H_301_12@?^@H_301_12@前缀表示从父节点查找并且失败后不抛出异常。然后便可以在@H_301_12@link@H_301_12@函数中使用已经注入好的@H_301_12@accordionController@H_301_12@了,调用@H_301_12@addExpander@H_301_12@方法将自己的作用域作为参数传入,以供@H_301_12@accordionController@H_301_12@访问其属性。然@H_301_12@
后在toggleText@H_301_12@方法中,除了要把自己的@H_301_12@showText@H_301_12@修改以外,还要调用@H_301_12@accordionController@H_301_12@的@H_301_12@gotOpended@H_301_12@方法通知父层指令把其他菜单给收缩起来。@H_301_12@
指令定义好后,我们就可以使用了,使用起来如下:
<accordion>
<expanderng-repeat="expanderinexpanders"etitle="expander.title">
{{expander.text}}
</expander>
外层使用了accordion@H_301_12@指令,内层使用@H_301_12@expander@H_301_12@指令,并且在@H_301_12@expander@H_301_12@上用@H_301_12@ng-repeat@H_301_12@循环输出子菜单。请注意这里遍历的数组@H_301_12@expanders@H_301_12@可不是@H_301_12@accordion@H_301_12@中定义的那个@H_301_12@expanders@H_301_12@,如果你这么认为了,说明还是对作用域不够了解。此@H_301_12@expanders@H_301_12@是@H_301_12@ng-repeat@H_301_12@的值,它是在外层@H_301_12@controller@H_301_12@中的,所以,在@H_301_12@testC@H_301_12@中,我们需要添加如下数据:@H_301_12@
$scope.expanders=[
{title:'个人简介@H_301_12@',sans-serif; font-size:16px"> text:'大家好,我是一名前端工程师,我正在研究@H_301_12@AngularJs@H_301_12@,欢迎大家与我交流@H_301_12@'},sans-serif; font-size:16px"> {title:'我的爱好@H_301_12@',sans-serif; font-size:16px"> text:'LOL'},sans-serif; font-size:16px"> {title:'性格@H_301_12@',sans-serif; font-size:16px"> text:'我的性格就是无性格'}@H_301_12@
];
6.3性能及调优@H_301_12@
6.3.1性能测试@H_301_12@
AnglarJS作为一款优秀的@H_301_12@Web@H_301_12@框架,可大大简化前端开发的负担。@H_301_12@
AnglarJS很棒,但当处理包含复杂数据结构的大型列表时,其运行速度就会非常慢。@H_301_12@
这是我们将核心管理页面迁移到AngularJS@H_301_12@过程中遇到的问题。这些页面在显示@H_301_12@500@H_301_12@行数据时本应该工作顺畅,但首个方法的渲染时间竟花费了@H_301_12@7@H_301_12@秒,太可怕了。后来,我们发现了在实现过程中存在两个主要性能问题。一个与“@H_301_12@ng-repeat@H_301_12@”指令有关,另一个与过滤器有关。@H_301_12@
AngularJS中的@H_301_12@ng-repeat@H_301_12@在处理大型列表时,速度为什么会变慢?@H_301_12@
AngularJS中的@H_301_12@ng-repeat@H_301_12@在处理@H_301_12@2500@H_301_12@个以上的双向数据绑定时速度会变慢。这是由于@H_301_12@AngularJS@H_301_12@通过“@H_301_12@dirtychecking@H_301_12@”函数来检测变化。每次检测都会花费时间,所以包含复杂数据结构的大型列表将降低你应用的运行速度。@H_301_12@
提高性能的先决条件
时间记录指令
为了测量一个列表渲染所花费的时间,我们写了一个简单的程序,通过使用“ng-repeat@H_301_12@”的属性“@H_301_12@$last@H_301_12@”来记录时间。时间存放在@H_301_12@TimeTracker@H_301_12@服务中,这样时间记录就与服务器端的数据加载分开了。@H_301_12@
//Postrepeatdirectiveforloggingtherenderingtime
angular.module('siApp.services').directive('postRepeatDirective',sans-serif; font-size:16px"> ['$timeout','$log','TimeTracker',sans-serif; font-size:16px"> function($timeout,$log,TimeTracker){
returnfunction(scope,sans-serif; font-size:16px"> if(scope.$last){
$timeout(function(){
vartimeFinishedLoadingList=TimeTracker.reviewListLoaded();
varref=newDate(timeFinishedLoadingList);
varend=newDate();
$log.debug("##DOMrenderinglisttook:"+(end-ref)+"ms");
]);
//UseinHTML:
<trng-repeat="iteminitems"post-repeat-directive>…</tr>
Chrome开发者工具的时间轴(@H_301_12@Timeline@H_301_12@)属性@H_301_12@
在Chrome@H_301_12@开发者工具的时间轴标签中,你可以看见事件、每秒内浏览器帧数和内存分配。“@H_301_12@memory@H_301_12@”工具用来检测内存泄漏,及页面所需的内存。当帧速率每秒低于@H_301_12@30@H_301_12@帧时就会出现页面闪烁问题。“@H_301_12@frames@H_301_12@”工具可帮助了解渲染性能,还可显示出一个@H_301_12@JavaScript@H_301_12@任务所花费的@H_301_12@cpu@H_301_12@时间。@H_301_12@
通过限制列表的大小进行基本的调优
缓解该问题,最好的办法是限制所显示列表的大小。可通过分页、添加无限滚动条来实现。
分页,我们可以使用AngularJS@H_301_12@的“@H_301_12@limitTo@H_301_12@”过滤器(@H_301_12@AngularJS1.1.4@H_301_12@版本以后)和“@H_301_12@startFrom@H_301_12@”过滤器。可以通过限制显示列表的大小来减少渲染时间。这是减少渲染时间最高效的方法。@H_301_12@
6.3.2七大调优法则
1.渲染没有数据绑定的列表@H_301_12@
这是最明显的解决方案,因为数据绑定是性能问题最可能的根源。如果你只想显示一次列表,并不需要更新、改变数据,放弃数据绑定是绝佳的办法。不过可惜的是,你会失去对数据的控制权,但除了该法,我们别无选择。
为了在控制器中直接过滤列表,不要使用可获得过滤链接的方法。“ng-repeat@H_301_12@”会评估每个表达式。在我们的案例中,“@H_301_12@filteredItems()@H_301_12@”返回过滤链接。如果评估过程很慢,它将迅速降低整个应用的速度。@H_301_12@
l<ling-repeat="iteminfilteredItems()">//这并不是一个好方法,因为要频繁地评估。@H_301_12@
l<ling-repeat="iteminitems">//这是要采用的方法@H_301_12@
3.使用两个列表(一个用来进行视图显示,一个作为数据源)@H_301_12@
将要显示的列表与总的数据列表分开,是非常有用的模型。你可以对一些过滤进行预处理,并将存于缓存中的链接应用到视图上。下面案例展示了基本实现过程。filteredLists@H_301_12@变量保存着缓存中的链接,@H_301_12@applyFilter@H_301_12@方法来处理映射。@H_301_12@
/*Controller*/
//Basiclist
varitems=[{name:"John",active:true},{name:"Adam"},{name:"Chris"},{name:"Heather"}];
//InitdisplayedList
$scope.displayedItems=items;
//FilterCache
varfilteredLists['active']=$filter('filter)(items,{"active":true});
//Applythefilter
$scope.applyFilter=function(type){
if(filteredLists.hasOwnProperty(type){//Checkiffilteriscached
$scope.displayedItems=filteredLists[type];
}else{
/*Noncachedfiltering*/
//Resetfilter
$scope.resetFilter=function(){
/*View*/
<buttonng-click="applyFilter('active')">Selectactive</button>
<ul><ling-repeat="itemindisplayedItems">{{item.name}}<li></ul>
4.在其他模板中使用@H_301_12@ng-if@H_301_12@来代替@H_301_12@ng-show@H_301_12@
如果你用指令、模板来渲染额外的信息,例如通过点击来显示列表项的详细信息,一定要使用ng-if@H_301_12@(@H_301_12@AngularJSv.1.1.5@H_301_12@以后)。@H_301_12@ng-if@H_301_12@可阻止渲染(与@H_301_12@ng-show@H_301_12@相比)。所以其它@H_301_12@DOM@H_301_12@和数据绑定可根据需要进行评估。@H_301_12@
<ling-repeat="iteminitems">
<p>{{item.title}}</p>
<buttonng-click="item.showDetails=!item.showDetails">Showdetails</buttons>
<divng-if="item.showDetails">
{{item.details}}
5.不要使用@H_301_12@ng-mouseenter@H_301_12@、@H_301_12@ng-mouseleave@H_301_12@等指令@H_301_12@
使用内部指令,像ng-mouseenter@H_301_12@,@H_301_12@AngularJS@H_301_12@会使你的页面闪烁。浏览器的帧速率通常低于每秒@H_301_12@30@H_301_12@帧。使用@H_301_12@jQuery@H_301_12@创建动画、鼠标悬浮效果可以解决该问题。确保将鼠标事件放入@H_301_12@jQuery@H_301_12@的@H_301_12@.live()@H_301_12@函数中。@H_301_12@
6.关于过滤的小提示:通过@H_301_12@ng-show@H_301_12@隐藏多余的元素@H_301_12@
对于长列表,使用过滤同样会减低工作效率,因为每个过滤都会创建一个原始列表的子链接。在很多情况下,数据没有变化,过滤结果也会保持不变。所以对数据列表进行预过滤,并根据情况将它应用到视图中,会大大节约处理时间。
在ng-repeat@H_301_12@指令中使用过滤器,每个过滤器会返回一个原始链接的子集。@H_301_12@AngularJS@H_301_12@从@H_301_12@DOM@H_301_12@中移除多余元素(通过调用@H_301_12@$destroy@H_301_12@),同时也会从@H_301_12@$scope@H_301_12@中移除他们。当过滤器的输入发生改变时,子集也会随着变化,元素必须进行重新链接,或着再调用@H_301_12@$destroy@H_301_12@。@H_301_12@
大部分情况下,这样做很好,但一旦用户经常过滤,或者列表非常巨大,不断的链接与
销毁将影响性能。为了加快过滤的速度,你可以使用ng-show@H_301_12@和@H_301_12@ng-hide@H_301_12@指令。在控制器中,进行过滤,并为每项添加一个属性。依靠该属性来触发@H_301_12@ng-show@H_301_12@。结果是,只为这些元素增加@H_301_12@ng-hide@H_301_12@类,来代替将它们移除子列表、@H_301_12@$scope@H_301_12@和@H_301_12@DOM@H_301_12@。@H_301_12@
触发ng-show@H_301_12@的方法之一是使用表达式语法。@H_301_12@ng-show@H_301_12@的值由表达式语法来确定。可以看下面的例子:@H_301_12@
<inputng-model="query"></input>
<ling-repeat="iteminitems"ng-show="([item.name]|filter:query).length">{{item.name}}</li>
<spanstyle="font-size:14px;line-height:24px;font-family:;white-space:normal;"></span>
解决第6@H_301_12@点提出的持续过滤问题的另一个方法是防抖动用户输入。例如,如果用户输入一个搜索关键词,只当用户停止输入后,过滤器才会被激活。使用该防抖动服务的一个很好的解决方案请见:@H_301_12@http://jsfiddle.NET/Warspawn/6K7Kd/@H_301_12@。将它应用到你的视图及控制器中,如下所示:@H_301_12@
/*Controller*/
//WatchthequeryInputanddebouncethefilteringby350ms.
$scope.$watch('queryInput',sans-serif; font-size:16px"> $debounce(applyQuery,350);
varapplyQuery=function(){
$scope.filter.query=$scope.query;
<inputng-model="queryInput"/>
<ling-repeat=iteminitems|filter:filter.query>{{item.title}}</li>
7总结
angular上手比较难,初学者(特别是习惯了使用@H_301_12@JQ@H_301_12@uery的人)可能不太适应其语法以及思想。随着对ng@H_301_12@探索的一步步深入,也确实感觉到了这一点,尤其是框架内部的某些执行机制。@H_301_12@
7.1页面效果@H_301_12@
ng-showng-hide无动画效果问题
7.2委派事件(代理事件)@H_301_12@
7.2.1NG循环及事件绑定@H_301_12@
Ng会根据array@H_301_12@的长度复制出@H_301_12@n@H_301_12@个@H_301_12@<li>@H_301_12@标签。而复制出的@H_301_12@<li>@H_301_12@节点中还有@H_301_12@<input>@H_301_12@节点并且使用了@H_301_12@ng-modle@H_301_12@指令,所以@H_301_12@ng会对所有的<input>@H_301_12@绑定监听器(事件)。如果@H_301_12@array很大,就会绑定太多的事件,性能出现问题。
7.2.2jQuery委派事件@H_301_12@
从jQuery1.7@H_301_12@开始,提供了@H_301_12@.on()@H_301_12@附加事件处理程序。@H_301_12@
.on(events[,selector][,data],handler(eventObject))
参数Selector为一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素。如果选择器是null@H_301_12@或者忽略了该选择器,那么被选中的元素总是能触发事件。@H_301_12@
如果省略selector@H_301_12@或者是@H_301_12@null@H_301_12@,那么事件处理程序被称为直接事件或者直接绑定事件。每次选中的元素触发事件时,就会执行处理程序,不管它直接绑定在元素上,还是从后代(内部)元素冒泡到该元素的。@H_301_12@
当提供selector@H_301_12@参数时,事件处理程序是指为委派事件(代理事件)。事件不会在直接绑定的元素上触发,但当@H_301_12@selector@H_301_12@参数选择器匹配到后代(内部元素)的时候,事件处理函数才会被触发。@H_301_12@jQuery@H_301_12@会从@H_301_12@eventtarget@H_301_12@开始向上层元素@H_301_12@(@H_301_12@例如,由最内层元素到最外层元素@H_301_12@)@H_301_12@开始冒泡,并且在传播路径上所有绑定了相同事件的元素若满足匹配的选择器,那么这些元素上的事件也会被触发。@H_301_12@
委托事件有两个优势:他们能在后代元素添加到文档后,可以处理这些事件;代理事件的另一个好处就是,当需要监视很多元素的时候,代理事件的开销更小。
例如,在一个表格的tbody@H_301_12@中含有@H_301_12@1,000@H_301_12@行,下面这个例子会为这@H_301_12@1,000@H_301_12@元素绑定事@H_301_12@
$("#dataTabletbodytr").on("click",function(event){alert($(this).text());});
委派事件的方法只有一个元素的事件处理程序,tbody@H_301_12@,并且事件只会向上冒泡一层(从被点击的@H_301_12@tr@H_301_12@到@H_301_12@tbody@H_301_12@)@H_301_12@:@H_301_12@
$("#dataTabletbody").on("click","tr",sans-serif; font-size:16px"> 许多委派的事件处理程序绑定到document@H_301_12@树的顶层附近,可以降低性能。每次发生事件时,@H_301_12@jQuery@H_301_12@需要比较从@H_301_12@eventtarget@H_301_12@(目标元素)开始到文档顶部的路径中每一个元素上所有该类型的事件。为了获得更好的性能,在绑定代理事件时,绑定的元素最好尽可能的靠近目标元素。避免在大型文档中,过多的在@H_301_12@document@H_301_12@或@H_301_12@document.body@H_301_12@上添加代理事件。@H_301_12@