admin管理员组文章数量:1568737
1前言
前端技术的发展是如此之快,各种优秀技术,优秀框架的出现简直让人目不暇接,紧跟时代潮流,学习掌握新知识自然是不敢怠慢。
AngularJS是谷歌在维护,其在国外已经十分火热,可是国内的使用情况却有不小的差距,参考文献/网络文章也很匮乏。这里便将我学习AngularJS写成文档,一方面作为自己学习路程上的记录,另一方面也给有兴趣的同学一些参考。
首先我自己也是一名学习者,会以学习者的角度来整理我的行文思路,这里可能只是些探索,有理解或是技术上的错误还请大家指出;其次我特别喜欢编写小例子来把一件事情说明白,故在文中会尽可能多的用示例加代码讲解,我相信这会是一种比较好的方式;最后,我深知AngularJS的使用方式跟的jquery的的使用方式有很大不同,在大家都有的jquery的,电话分机经验的条件下对于角的学习会困难重重,不过我更相信在大家的坚持下,能够快速的学好AngularJS,至少咱也能深入了解到AngularJS的基本思想,对咱们以后自己的插件开发,项目开发都会有很大的启示。
2 AngularJS概述
2.1 AngularJS是什么?
AngularJs(后面就简称NG了)是一个用于设计动态的网络应用的结构框架。首先,它是一个框架,不是类库,是像EXT一样提供一整套方案用于设计网络应用。它不仅仅是一个JavaScript的的框架,因为它的核心其实是对HTML标签的增强。
何为HTML标签增强其实就是使你能够用标签完成一部分页面逻辑,具体方式就是通过自定义标签,自定义属性等,这些HTML原生没有的标签/属性在NG中有一个名字:指令(指令)。后面会详细介绍。那么,什么又是动态的网络应用呢?与传统的网络系统相区别,网络应用能为用户提供丰富的操作,能够随用户操作不断更新视图而不进行URL跳转.ng官方也声明它更适用于开发CRUD应用,即数据操作比较多的应用,而非是游戏或图像处理类应用。
为了实现这些,NG引入了一些非常棒的特性,包括模板机制,数据绑定,模块,指令,依赖注入,路由。通过数据与模板的绑定,能够让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上。
源地址:https://blog.csdn/weixin_33178524/article/details/79179597
另外一个疑问,NG是MVC框架吗?还是MVVM框架?官网有提到纳克的设计采用了MVC的基本思想,而又不完全是MVC,因为在书写代码时我们确实是在用NG-控制器这个指令(起码从名字上看,是MVC吧),但这个控制器处理的业务基本上都是与图进行交互,这么看来又很接近MVVM让我们把目光移到官网那个非醒目的标题上:“AngularJS - 超级英雄JavaScript MVW框架”。
2.2 AngularJS简单介绍
AngularJS重新定义了前端应用的开发方式。面对HTML和JavaScript的之间的界线,它
非但不畏缩不前,反而正面出击,提出了有效的解决方案。
很多前端应用的开发框架,比如骨干,EmberJS等,都要求开发者继承此框架特有的一些JavaScript的的对象。这种方式有其长处,但它不必要地污染了开发者自己代码的对象空间,还要求开发者去了解内存里那些抽象对象。尽管如此我们还是接受了这种方式,因为网络最初的设计无法提供我们今天所需的交互性,于是我们需要框架,来帮我们填补JavaScript的HTML的和之间的鸿沟。而且有了它,你不用再“直接”操控DOM,只要给你的DOM注上的元数据(即AngularJS里的指令们),然后让AngularJS来帮你操纵DOM。同时,AngularJS不依赖(也不妨碍)任何其他的框架。你甚至可以基于其它的框架来开发AngularJS应用。
API地址:http://docs.angularjs/api/ ;
AngularJS在github上的中文粗译版地址:https://github/basestyle/angularjs-cn。
2.3什么时候该用AngularJS
AngularJS是一个MV *框架,最适合开发客户端的单页面应用。它不是个功能库,而是用来开发动态网页的框架。它专注于扩展HTML的功能,提供动态数据绑定(数据绑定) ,而且它能跟其它框架(如jQuery的)合作融洽。
如果你要开发的是单页应用,AngularJS就是你的上上之选.Gmail,Google Docs,Twitter和Facebook这样的应用,都很能发挥AngularJS的长处。但是像游戏开发之类对DOM进行大量操纵,又或者单纯需要极高运行速度的应用,就不是AngularJS的用武之地了。
3 AugularJS特性
AngularJS是一个新出现的强大客户端技术,提供给大家的一种开发强大应用的方式。这种方式利用并且扩展HTML,CSS和JavaScript的,并且弥补了它们的一些非常明显的不足。本应该使用HTML来实现而现在由它开发的动态一些内容。
AngularJS有五个最重要的功能和特性:
3.1特性一:双向的数据绑定
数据绑定可能是AngularJS最酷最实用的特性。它能够帮助你避免书写大量的初始代码从而节约开发时间。一个典型的网络应用可能包含了80%的代码用来处理,查询和监听DOM。数据绑定是的代码更少,你可以专注于你的应用。
我们想象一下模型是你的应用中的简单事实。你的模型是你用来读取或者更新的部分。数据绑定指令提供了你的模型投射到视图的方法。这些投射可以无缝的,毫不影响的应用到网络应用中。
传统来说,当模型变化了。开发人员需要手动处理DOM元素并且将属性反映到这些变化中。这个一个双向的过程。一方面,模型变化驱动了DOM中元素变化,另一方面,DOM元素的变化也会影响到模型。这个在用户互动中更加复杂,因为开发人员需要处理和解析
这些互动,然后融合到一个模型中,并且更新视图。这是一个手动的复杂过程,当一个应用非常庞大的时候,将会是一件非常费劲的事情。
这里肯定有更好的解决方案!那就是AngularJS的双向数据绑定,能够同步DOM和型号等等。
这里有一个非常简单的例子,用来演示一个输入输入框和<H1>元素的双向绑定(例01):
<!doctype html>
<html ng-app =“demoApp”>
<HEAD>
<script src =“./ js / angular.min.js”> </ script>
</ HEAD>
<BODY>
<DIV>
<标签>名称:</标签>
<input type =“text”ng-model =“user.name”placeholder =“请输入名字”>
<HR>
<h1>您好,{{user.name}}!</ h1>
</ DIV>
</ BODY>
</ HTML>
说明:实际效果请大家看AngularJS /演示/ index.html的
3.2特性二:模板
在AngularJS中,一个模板就是一个HTML文件。但是HTML的内容扩展了,包含了很多帮助你映射模型到视图的内容。
HTML模板将会被浏览器解析到DOM中.DOM然后成为AngularJS编译器的输入.AngularJS将会遍历DOM模板来生成一些指导,即,指令(指令)。所有的指令都负责针对视图来设置数据绑定。
我们要理解AuguarJS并不把模板当做字符串来操作。输入AngularJS的是DOM而非字符串。数据绑定是DOM变化,不是字符串的连接或者innerHTML的变化。使用DOM作为输入,而不是字符串,是AngularJS区别于其它的框架的最大原因。使用DOM允许你扩展指令词汇并且可以创建你自己的指令,甚至开发可重用的组件。
最大的好处是为设计师和开发者创建了一个紧密的工作流。设计师可以像往常一样开发标签,然后开发者拿过来添加上功能,通过数据绑定将会使得这个过程非常简单。
这里有一个例子,我们使用NG-重复指令来循环图片数组并且加入IMG模板,如下:
function AlbumCtrl($ scope){
scope.images = [
{“image”:“img / image_01.png”,“description”:“图片01说明”},
{“image”:“img / image_02.png”,“description”:“图片02说明”},
{“image”:“img / image_03.png”,“description”:“图片03说明”},
{“image”:“img / image_04.png”,“description”:“图片04说明”},
{“image”:“img / image_05.png”,“description”:“图片05说明”}
]。
}
<div ng-controller =“AlbumCtrl”>
<UL>
<li ng-repeat =“图像中的图像”>
<img ng-src =“{{image.thumbnail}}”alt =“{{image.description}}”>
</ LI>
</ UL>
</ DIV>
这里还有一件事值得提一句,AngularJS并不强制你学习一个新的语法或者从你的应用中提出你的模板。
3.3特性三:MVC
针对客户端应用开发AngularJS吸收了传统的MVC基本原则.MVC或者模型 - 视图 - 控制研究设计模式针对不同的人可能意味不同的东西.AngularJS并不执行传统意义上的MVC,更接近于MVVM(Moodel-视图查看模型)。
模型
model是应用中的简单数据。一般是简单的javascript对象。这里没有必要继承框架的类,使用代理对象封装或者使用特别的setter / getter方法来访问。事实上我们处理vanilla javascript的方法就是一个非常好的特性,这种方法使得我们更少使用应用的原型。
视图模型
视图模型是一个用来提供特别数据和方法从而维护指定视图的对象。
视图模型是$范围的对象,只存在于AnguarJS的应用中。$范围只是一个简单的JS对象,这个对象使用简单的API来侦测和广播状态变化。
调节器
控制器负责设置初始状态和参数化$范围方法用以控制行为。需要指出的控制器并不保存状态也不和远程服务互动。
视图
查看是AngularJS解析后渲染和绑定后生成的HTML。这个部分帮助你创建web应用的架构。$ scope拥有一个针对数据的参考,控制器定义行为,查看处理布局和互动。
3.4特性四:服务和依赖注入
AngularJS服务其作用就是对外提供某个特定的功能。
AngularJS拥有内建的依赖注入(DI)子系统,可以帮助开发人员更容易的开发,理解和测试应用。
DI允许你请求你的依赖,而不是自己找寻它们。比如,我们需要一个东西,DI负责找创建并且提供给我们。
为了而得到核心的AngularJS服务,只需要添加一个简单服务作为参数,AngularJS会侦测并且提供给你:
function EditCtrl($ scope,$ location,$ routeParams){
//这里有点聪明......
}
你也可以定义自己的服务并且让它们注入:
angular.module('MyServiceModule',[])。
factory('notify',['$ window',function(win){
return函数(msg){
win.alert(MSG);
};
}]);
function myController(scope,notifyService){
scope.callNotify = function(msg){
notifyService(MSG);
};
}
myController。$ inject = ['$ scope','notify'];
3.5特性五:指令(指令)
指令是我个人最喜欢的特性。你是不是也希望浏览器可以做点儿有意思的事情?那么AngularJS可以做到。
指令可以用来创建自定义的标签。它们可以用来装饰元素或者操作DOM属性。可以作为标签,属性,注释和类名使用。
这里是一个例子,它监听一个事件并且针对的更新它的$ scope,如下:
myModule.directive('myComponent',function(mySharedService){
返回{
限制:'E',
controller:function($ scope,$ attrs,mySharedService){
$ scope。$ on('handleBroadcast',function(){
$ scope.message ='指令:'+ mySharedService.message;
});
},
替换:true,
模板:'<input>'
};
});
然后,你可以使用这个自定义的指令来使用:
<my-component ng-model =“message”> </ my-component>
使用一系列的组件来创建你自己的应用将会让你更方便的添加,删除和更新功能。
4功能介绍
4.1数据绑定
AngularJS的双向数据绑定,意味着你可以在模式(JS)中改变数据,而这些变动立刻就会自动出现在视图上,反之亦然即:一方面可以做到模型变化驱动了DOM中元素变化,另一方面也可以做到DOM元素的变化也会影响到模型。
在我们使用jQuery的时候,代码中会大量充斥类似这样的语句:var val = $('#id')。val(); $( '#ID')HTML(STR);等等,即频繁的DOM操作(读取和写入),其实我们的最终目的并不是要操作DOM,而是要实现业务逻辑.ng的绑定将让你摆脱DOM操作,只要模板与数据通过声明进行了绑定,两者将随时保持同步,最新的数据会实时显示在页面中,页面中用户修改的数据也会实时被记录在数据模型中。
从景观到控制器再到视图的数据交互(例01):
<html ng-app =“demoApp”>
......
<input type =“text”ng-model =“user.name”placeholder =“请输入名称”/>
您好,{{user.name}}!
......
关键词:ng-app,ng-model和{{user.name}}
首先:<html>元素的ng-app属性。标识这个DOM里面的内容将启用AngularJS应用。
其次:告诉AngularJS,对页面上的“user.name”这个模型进行双向数据绑定。
第三:告诉AngularJS,在“{{user.name}}”这个指令模版上显示“user.name”这个模型的数据。
从服务器到控制器再到视图的数据交互(例02):
<html ng-app =“demoApp”>
......
<div ng-controller =“demoController”>
<input type =“text”ng-model =“user.name”disabled =“disabled”/>
<a href =“javascript:void(0);” NG-点击= “getAjaxUser()”> AJAX获取名字</A>
......
demoApp.controller(“demoController”,function($ http,$ scope){
$范围。getAjaxUser = function(){
// $ http.get({url:“../ xxx.action”})。success(function(data){
// $ scope.user = data;
//});
$ scope.user = {“name”:“从JOSN中获取的名称”,“年龄”:22};
};
});
改变$范围中的用户,查看也会自动更新。
4.2范围,模块,控制器
4.2.1范围
$ scope是一个把视图(一个DOM元素)连结到控制器上的对象。在我们的MVC结构里,这个$ scope将成为模型,它提供一个绑定到DOM元素(以及其子元素)上的执行上下文。
尽管听起来有点复杂,但$ scope实际上就是一个JavaScript对象,控制器和视图都可以访问它,所以我们可以利用它在两者间传递信息。在这个$ scope对象里,我们既存储数据,又存储将要运行在视图上的函数。
每一个Angular应用都会有一个$ rootScope。这个$ rootScope是最顶级的范围,它对应着含有ng-app指令属性的那个DOM元素。
app.run(function($ rootScope){$ rootScope.name =“张三”;});
如果页面上没有明确设定$ scope,Angular就会把数据和函数都绑定到这里,第一部分中的例子就是靠这一点成功运行的。
这样,我们就可以在视图的任何地方访问这个名称属性,使用模版表达式{{}},像这样:
{{ 名称 }}
4.2.2模块
首先需要明确一下模板的概念。在我还不知道有模板这个东西的时候,曾经用JS拼接出很长的HTML字符串,然后追加到页面中,这种方式想想真是又土又笨。后来又看到可以把HTML代码包裹在一个<SCRIPT>标签中当作模板,然后按需要取来使用。
在NG中,模板十分简单,它就是我们页面上的HTML代码,不需要附加任何额外的东西。在模板中可以使用各种指令来增强它的功能,这些指令可以让你把模板和数据巧妙的绑定起来。
在<HTML>标签上多了一个属性纳克应用内=” MyApp的”,它的作用就是用来指定纳克的作用域是在<html>的标签以内部分。在JS中,我们调用角度对象的模块方法来声明一个模块,模块的名字和纳克应用内的值对应。这样声明一下就可以让纳克运行起来了。
示例:
<html ng-app =“demoApp”>
var demoApp = angular.module('demoApp',[]);
4.2.3 ng-controller
要明确创建一个$ scope对象,我们就要给DOM元素安上一个控制器对象,使用的是ng-controller指令属性:
<div ng-controller =“MyController”> {{person.name}} </ div>
ng-controller指令给所在的DOM元素创建了一个新的$ scope对象,并将这个$ scope对象包含进外层DOM元素的$ scope对象里。在上面的例子里,这个外层DOM元素的$ scope对象,就是$ rootScope对象。这个范围链是这样的:
所有范围都遵循原型继承(prototypal inheritance),这意味着它们都能访问父范围。对任何属性和方法,如果AngularJS在当前范围上找不到,就会到父范围上去找,如果在父范围上也没找到,就会继续向上回溯,一直到$ rootScope上。即如果控制器是多层嵌套的,就会从最里面一直往外找,这个范围链是这样的:
唯一的例外:有些指令属性可以选择性地创建一个独立的范围,让这个范围不继承它的父范围们,这个会在指令详解中说明。
4.3 ajax
$ http服务是AngularJS的核心服务之一,它帮助我们通过XMLHttpRequest对象或JSONP与远程HTTP服务进行交流。
$ http服务是这样一个函数:它接受一个设置对象,其中指定了如何创建HTTP请求;它将返回一个承诺(*参考JavaScript异步编程的承诺模式),其中提供两个方法:成功方法和错误方法。
demoApp.controller(“demoController”,function($ http,$ scope){
$范围。getAjaxUser = function(){
$ http.get(网址:{url: “../ xxx.action”})成功(功能(数据){
警报(数据);
})。误差(函数(){
警报(“出错了!”);
});
};
});
AngularJS的AJAX与jQuery的等框架的AJAX基本一致,这里就不多说了。
4.4表达式
NG中的表达式与JavaScript的表达式类似但是不可以划等号,它是NG自己定义的一套模式。表达式可以作为指令的值,如NG-MODLE =” people.name”,NG-单击= “SHOWME()”,看起来是如此像字符串,故而也叫字符串表达式。也可以在标记中使用表达式,如{{1 + 2}},或者与过滤器一起使用{{1+ 2 | 货币}}。在框架内部,字符串不会简单的使用eval()函数来执行,而是有一个专门的$解析服务来处理。在NG表达式中不可以使用循环语句,判断语句,事实上在模板中使用复杂的表达式也是一个不推荐的做法,这样视图与逻辑就混杂在一起了
我们在使用其他模板库时,一般都会有模板的循环输出,分支输出,逻辑判断等类似的控制。
要想理解指令属性的运作,我们必须先理解表达式。在之前的例子里我们已经见过表达式,例如{{user.name}}。
请查看例03,例04,例05。
{{8 + 1}} 9
{{person}} {“name”:“Ari Lerner”}
{{10 * 3.3 | 货币}} $ 33.00
表达式粗略来看有点像eval(javascript)的结果。它们会经过Angular.js的处理,从而拥有以下重要而独特的性质:
l所有表达式都在范围这个上下文里被执行,因此可以使用所有本地$ scope中的变量。
l如果一个表达式的执行导致类型错误或引用错误,这些错误将不会被抛出。
l表达式里不允许任何控制函数流程的功能(如if / else等条件语句)
l表达式可接受一个或多个串联起来的过滤器。
4.5过滤器
过滤器(过滤器)正如其名,作用就是接收一个输入,通过某个规则进行处理,然后返回处理后的结果。主要用在数据的格式化上,例如获取一个数组中的子集,对数组中的元素进行排序等。过滤器通常是伴随标记来使用的,将你的模型中的数据格式化为需要的格式。表单的控制功能主要涉及到数据验证以及表单控件的增强.ng内置了一些过滤器,它们是:
货币(货币),日期(日期),过滤器(子串匹配),JSON(格式化JSON对象),limitTo(限制个数),小写(小写),大写(大写),数(数字),ORDERBY(排序)。
4.5.1过滤器使用方式
总共九种。除此之外还可以自定义过滤器,这个就强大了,可以满足任何要求的数据处理.Filter还是很简单的,需要明白的是内置的过滤器如何使用,以及自己如何定义一个过滤器。
过滤器的两种使用方法:
1.在模板中使用过滤器
我们可以直接在{{}}中使用filter,跟在表达式后面用| 分割,语法如下:
{{表达| 过滤}}
也可以多个过滤器连用,上一个滤波器的输出将作为下一个过滤器的输入:
{{表达| filter1 | filter2 | ...}}
filter可以接收参数,参数用:进行分割,如下:
{{表达| filter:argument1:argument2:...}}
除了对{{}}中的数据进行格式化,我们还可以在指令中使用过滤器,例如先对数组阵列进行过滤处理,然后再循环输出:
<span ng-repeat =“a in array | filter”>
2.在控制器和服务中使用过滤器
我们的JS代码中也可以使用过滤器,方式就是我们熟悉的依赖注入,例如我要在控制器中使用的货币过滤器,只需将它注入到该控制器中即可,代码如下:
app.controller( 'TESTC',函数($范围,currencyFilter){
$ scope.num = currencyFilter(123534);
}
在模板中使用{{NUM}}就可以直接输出$ 123,534.00了!在服务中使用过滤器也是同样的道理。
如果你要在控制器中使用多个过滤器,并不需要一个一个注入吗,NG提供了一个$过滤服务可以来调用所需的过滤器,你只需注入一个$过滤器就够了,使用方法如下:
app.controller( 'TESTC',函数($范围,$滤波器){
$ scope.num = $ filter('currency')(123534);
$ scope.date = $ filter('date')(new Date());
}
可以达到同样的效果。好处是你可以方便使用不同的过滤器了。
4.5.2 ng的内置过滤器
纳克内置了九种过滤器,使用方法都非常简单,看文档即懂。不过为了以后不去翻它的文档,我在这里还是做一个详细的记录。
货币(货币),日期(日期),过滤器(子串匹配),JSON(格式化JSON对象),limitTo(限制个数),小写(小写),大写(大写),数(数字),ORDERBY(排序)
1. currency(货币处理)
使用货币可以将数字格式化为货币,默认是美元符号,你可以自己传入所需的符号,例如我传入人民币:
{{num | 货币:'¥'}}
2. date(日期格式化)
原生的JS对日期的格式化能力有限,NG提供的最新过滤器基本可以满足一般的格式化要求用法如下:
{{date | 日期:'yyyy-MM-dd hh:mm:ss EEEE'}}
参数用来指定所要的格式,y M dhms E分别表示年月时分分秒星期,你可以自由组合它们。也可以使用不同的个数来限制格式化的位数。另外参数也可以使用特定的描述性字符串,例如“shortTime”将会把时间格式为12:05 pm这样的.ng提供了八种描述性的字符串,个人觉得这些有点多余,我完全可以根据自己的意愿组合出想要的格式,不愿意去记这么多单词〜
3. filter(匹配子串)
这个名叫滤波器的滤波器。用来处理一个数组,然后可以过滤出含有某个子串的元素,作为一个子数组来返回。可以是字符串数组,也可以是对象数组。如果是对象数组,可以匹配属性的值它接收一个参数,用来定义子串的匹配规则下面举个例子说明一下参数的用法,我用现在特别火的几个孩子定义了一个数组。:
$ scope.childrenArray = [
{名称: '君',年龄:3},
{名称: '辛迪',年龄:4},
{名称: 'anglar',年龄:4},
{名称: '石头',年龄:6},
{名称: '甜甜',年龄:5}
]。
$ scope.func = function(e){return e.age> 4;} {{childrenArray | filter:'a'}} //匹配属性值中含有a的
{{childrenArray | filter:4}} //匹配属性值中含有4的
{{ childrenArray | filter : {name : 'i'} }} //参数是对象,匹配name属性中含有i的
{{childrenArray | filter : func }} //参数是函数,指定返回age>4的
4. json(格式化json对象)
json过滤器可以把一个js对象格式化为json字符串,没有参数。这东西有什么用呢,我一般也不会在页面上输出一个json串啊,官网说它可以用来进行调试,嗯,是个不错的选择。或者,也可以用在js中使用,作用就和我们熟悉的JSON.stringify()一样。用法超级简单:
{{ jsonTest | json}}
5. limitTo(限制数组长度或字符串长度)
limitTo过滤器用来截取数组或字符串,接收一个参数用来指定截取的长度,如果参数是负值,则从数组尾部开始截取。个人觉得这个filter有点鸡肋,首先只能从数组或字符串的开头/尾部进行截取,其次,js原生的函数就可以代替它了,看看怎么用吧:
{{ childrenArray | limitTo : 2 }} //将会显示数组中的前两项
6. lowercase(小写)
把数据转化为全部小写。太简单了,不多解释。同样是很鸡肋的一个filter,没有参数,只能把整个字符串变为小写,不能指定字母。怎么用我都懒得写了。
7. uppercase(大写)
同上。
8. number(格式化数字)
number过滤器可以为一个数字加上千位分割,像这样,123,456,789。同时接收一个参数,可以指定float类型保留几位小数:
{{ num | number : 2 }}
9. orderBy(排序)
orderBy过滤器可以将一个数组中的元素进行排序,接收一个参数来指定排序规则,参数可以是一个字符串,表示以该属性名称进行排序。可以是一个函数,定义排序属性。还可以是一个数组,表示依次按数组中的属性值进行排序(若按第一项比较的值相等,再按第二项比较),还是拿上面的孩子数组举例:
<div>{{ childrenArray | orderBy : 'age' }}</div> //按age属性值进行排序,若是-age,则倒序
<div>{{ childrenArray | orderBy : orderFunc }}</div> //按照函数的返回值进行排序
<div>{{ childrenArray | orderBy : ['age','name'] }}</div> //如果age相同,按照name进行排序 内置的过滤器介绍完了,写的我都快睡着了。。。正如你所看到的,ng内置的过滤器也并不是万能的,事实上好多都比较鸡肋。更个性化的需求就需要我们来定义自己的过滤器了,下面来看看如何自定义过滤器。
4.5.3自定义过滤器及示例
filter的自定义方式也很简单,使用module的filter方法,返回一个函数,该函数接收
输入值,并返回处理后的结果。话不多说,我们来写一个看看。比如我需要一个过滤器,它可以返回一个数组中下标为奇数的元素,代码如下:
app.filter('odditems',function(){
return function(inputArray){
var array = [];
for(var i=0;i<inputArray.length;i++){
if(i%2!==0){
array.push(inputArray[i]);
}
}
返回数组;
}
});
格式就是这样,你的处理逻辑就写在内部的那个闭包函数中。你也可以让自己的过滤器接收参数,参数就定义在返回的那个函数中,作为第二个参数,或者更多个参数也可以。
自定义过滤器实例(例04):
/ *查看HTML * /
名字:<input ng-model =“user.firstName”/> <br/>
姓氏:<input ng-model =“user.lastName”/> <br/>
名字:{{user.firstName}}姓氏:{{user.lastName}} <br/>
全名:{{user | flFullname}} <BR/>
全名:{{user | flFullname: “ - ”}} <BR/>
全名:{{user | flFullname:“•”| 大写}}
/ *控制器js * /
demoApp.filter(“flFullname”,function(){
return function(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){return fullName;
} else {return“”;}
};
});
4.6指令(指令)
通过使用模板,我们可以把模型和控制器中的数据组装起来呈现给浏览器,还可以通过数据绑定,实时更新视图,让我们的页面变成动态的。
模板中可以使用的东西包括以下四种:
1.指令(指令):纳克提供的或者自定义的标签和属性,用来增强HTML表现力;
2.标记(标记):即双大括号{{}},可将数据单向绑定到HTML中;
3.过滤器(过滤器):用来格式化输出数据;
4.表单控制:用来增强表单的验证功能。
其中,指令无疑是使用量最大的,NG内置了很多指令用来控制模板,如纳克重复,纳克级的,也有很多指令来帮你完成业务逻辑,如纳克控制器,NG-模型。
指令的几种使用方式如下:
l作为标签:<my-dir> </ my-dir>
l作为属性:<span my-dir =“exp”> </ span>
l作为注释:<! - directive:my-dir exp - >
l作为类名:<span class =“my-dir:exp;”> </ span>
其实常用的就是作为标签和属性。
4.6.1样式相关的指令
既然模板就是普通的HTML,那我首要关心的就是样式的控制,元素的定位,字体,背景色等等如何可以灵活控制。下面来看看常用的样式控制指令。
1. ng级
纳克级用来给元素绑定类名,其表达式的返回值可以是以下三种:
l类名字符串,可以用空格分割多个类名,如'redtext boldtext';
l类名数组,数组中的每一项都会层叠起来生效;
l一个名值对应的地图,其键值为类名,值为布尔类型,当值为真时,该类会被加在元素上。
下面来看一个使用地图的例子:
纳克级测试
红色加粗删除线
map:{redtext:{{red}},boldtext:{{bold}},striketext:{{strike}}}
如果你想拼接一个类名出来,可以使用插值表达式,如:
<div class =“{{style}} text”>字体样式测试</ div>
然后在控制器中指定样式的值:
$ scope.style ='red';
注意我用了类而不是纳克级的,这是不可以对换的,官方的文档也未做说明,姑且认为这是NG的语法规则吧。
与纳克级相近的,NG还提供了纳克级的奇,纳克级,甚至两个指令,用来配合NG-重复分别在奇数列和偶数列使用对应的类。这个用来在表格中实现隔行换色再方便不过了。
2. ng风格
NG-风格用来绑定元素的CSS样式,其表达式的返回值为一个JS对象,键为CSS样式名,值为该样式对应的合法取值用法比较简单:
<div ng-style =“{color:'red'}”> ng-style测试</ div>
<div ng-style =“style”> ng-style测试</ div>
$ scope.style = {color:'red'};
3. ng-show,ng-hide
对于比较常用的元素显隐控制,NG也做了封装,NG-显示和NG-隐藏的值为布尔类型的表达式,当值为真时,对应的节目或隐藏生效框架会用显示:块和显示:无来控制元素的显隐。
4.6.2表单控件功能相关指令
对于常用的表单控件功能,NG也做了封装,方便灵活控制。
NG-检查控制无线电和复选框的选中状态
NG-选择控制下拉框的选中状态
NG-禁用控制失效状态
NG-多个控制多选
NG-只读控制只读状态
以上指令的取值均为布尔类型,当值为真时相关状态生效,道理比较简单就不多做解释。注意:上面的这些只是单向绑定,即只是从数据到模板,不能反作用于数据。要双向绑定,还是要使用ng-model。
4.6.3事件绑定相关指令
事件绑定是javascrpt中比较重要的一部分内容,NG对此也做了详细的封装,正如我们之前使用过的NG-点击一样,事件的指令如下:
NG-点击
NG-变化
NG-DBLCLICK
NG-鼠标按下
NG-的mouseenter
NG-鼠标离开
NG-鼠标移动
NG-鼠标悬停
NG-鼠标松开
NG-提交
事件绑定指令的取值为函数,并且需要加上括号,例如:
<select ng-change =“change($ event)”> </ select>
然后在控制器中定义如下:
$ scope.change = function($ event){
警报($ event.target);
// ........................
}
在模板中可以用变量$事件将事件对象传递到控制器中。
对于吴的这种设计,一些人有所质疑,视图与事件绑定混在一起到底好不好?我们不是要讲究视图与逻辑分离吗?如此一来,把事件的绑定又变回了内联的,岂不是历史的倒退。我也一样对此表示不解,因为不写的onclick已经很多年......但既然已经存在了,我们不妨往合理的方向上想一想,或许NG的设计者压根就不想让模板成为单纯的视图层,本来就是想增强HTML,让它有一点业务能力。这么想的话似乎也能想通,好吧,先欺骗一下自己吧〜
4.6.4特殊的NG-SRC和NG-HREF
在说明这两个指令的特殊之前,需要先了解一下纳克的启动及执行过程,如下图:
1)浏览器加载静态HTML文件并解析为DOM;
2)浏览器加载angular.js文件;
3)angular监听DOMContentLoaded事件,监听到时开始启动;
4)angular寻找ng-app指令,确定作用范围;
5)找到app中定义的模块使用$ injector服务进行依赖注入;
6)根据$ injector服务创建$ compile服务用于编译;
7)$ compile服务编译DOM中的指令,过滤器等;
8)使用ng-init指令,将作用域中的变量进行替换;
9)最后生成了我们在最终视图。
可以看到,NG框架是在DOMcontent加载完毕后才开始发挥作用假如我们模板中有一张图片如下:
<img src =“{{imgUrl}}”/>
那么在页面开始加载到纳克编译完成之前,页面上会一直显示一张错误的图片,因为路径{{imgUrl的}}还未被替换。
为了避免这种情况,我们使用NG-SRC指令,这样在路径被正确得到之前就不会显示找不到图片。同理,<A>标签的HREF属性也需要换成NG-HREF,这样页面上就不会先出现一个地址错误的链接。
顺着这个思路再多想一点,我们在模板中使用{{}}显示数据时,在NG编译完成之前页面上岂不是会显示出大括号及里面的表达式?确实是这样。为了避免这个,纳克中有一个与{{}}等同的指令:NG-结合,同样用于单向绑定,在页面刚加载的时候就不会显示出对用户无用的数据了尽管这样你可能不但没舒心反而更纠结了,{{}}那么好用易理解,还不能用了不成?好消息是我们依然可以使用。因为我编写的是单页面应用,页面只会在加载的index.html的时
候出这个问题,只需在的index.html中的模板中换成纳克绑定就行。其他的模板是我们动态加载的,就可以放心使用{{}}了。
4.6.5自定义指令示例
下面我们来解析下指令的例子(例07)。
1.首先,我们定义一个名为USERINFO的指令:
demoApp.directive( '用户信息',函数(){
返回{
限制:'E',
templateUrl:'userInfoTemplate.html',
替换:true,
转录:是的,
范围 : {
mytitle:'= etitle'
},
link:function(scope,element,attrs){
scope.showText = false;
scope.toggleText = function(){
scope.showText =!scope.showText;
}
}
};
})
限制为'E':用作标签;替换为真:用模板替换当前标签; transclude为true:将当前元素的内容转移到模板中; scope为{mytitle:'= etitle'}:定义一个名为mytitle的模型,其值指向当前元素的etitle属性; templateUrl为 'userInfoTemplate.html':模板内容为NG-模板定义ID为userInfoTemplate.html的内容;链接:指定所包含的行为其具体的说明及其他参数,请参考:6.2指令详解。
2. userInfoTemplate.html模板为:
<script type =“text / ng-template”id =“userInfoTemplate.html”>
<div class =“mybox”>
<div class =“mytitle”style =“cursor:pointer;” NG-点击= “toggleText()”>
{{mytitle}}
</ DIV>
<div ng-transclude ng-show =“showText”>
</ DIV>
</ DIV>
</ SCRIPT>
将当前元素的内容添加到有NG-transclude属性的这个DIV下,默认是隐藏的。
3.Controller信息:
demoApp.controller(“test7Controller”,function($ scope){
$ scope.title ='个人简介';
$ scope.text ='大家好,我正在研究AngularJs,欢迎大家与我交流。';
$ scope.updateInfo = function(){
$ scope.title ='个人信息';
$ scope.text ='大家好,今天天气真好!';
}
});
4.指令使用方式(查看信息)为:
<user-info etitle =“title”> {{text}} </ user-info>
Etitle指向控制器中的$ scope.title注意命名方式:指令名为用户信息,对应的标签为用户信息。
4.7服务(服务)
4.7.1服务介绍
服务这个概念其实并不陌生,在其他语言中如java的便有这样的概念,其作用就是对外提供某个特定的功能,如消息服务,文件压缩服务等,是一个独立的模块.ng的服务是这样定义的:
角度服务是执行Web应用程序常见的特定任务的单例对象或函数。
它是一个单例对象或函数,对外提供特定的功能。
首先是一个单例,即无论这个服务被注入到任何地方,对象始终只有一个实例。
其次这与我们自己定义一个函数然后在其他地方调用不同,因为服务被定义在一个模块中,所以其使用范围是可以被我们管理的.ng的避免全局变量污染意识非常强。
NG提供了很多内置的服务,可以到API中查看http://docs.angularjs/api/。知道了概念,我们来拉一个服务出来溜溜,看看到底是个什么用法。
我们在控制器中直接声明$位置服务,这依靠天然气的依赖注入机制。$位置提供地址栏相关的服务,我们在此只是简单的获取当前的地址。
服务的使用是如此简单,我们可以把服务注入到控制器,指令或者是其他服务中。
4.7.2自定义服务
如同指令一样,系统内置的服务以$开头,我们也可以自己定义一个服务。定义服务的方式有如下几种:
l 使用系统内置的$provide服务;
l 使用Module的factory方法;
l 使用Module的service方法。
下面通过一个小例子来分别试验一下。我们定义一个名为remoteData服务,它可以从远程获取数据,这也是我们在程序中经常使用的功能。不过我这里没有远程服务器,就写死一点数据模拟一下。
//使用$provide来定义
var app = angular.module('MyApp', [], function($provide) {
$provide.factory('remoteData', function() {
var data = {name:'n',value:'v'};
return data;
});
});
//使用factory方法
app.factory('remoteData',function(){
var data = {name:'n',value:'v'};
return data;
});
//使用service方法
app.service('remoteData',function(){
this.name = 'n';
this.value = 'v';
});
Module的factory和$provide的factory方法是一模一样的,从官网文档看它们其实就是一回事。至于Module内部是如何调用的,我此处并不打算深究,我只要知道怎么用就好了。
再看模块的服务方法,它没有任何东西,是回报因为服务方法本身返回一个构造器,系统会自动使用新的关键字来创建出一个对象。所以我们看到在构造器函数内可以使用这个,这样调用该服务的地方便可以直接通过remoteData.name来访问数据了。
4.7.3管理服务的依赖关系
服务与服务中间可以有依赖关系,例如我们这里定义一个名为验证的服务,它的作用是验证数据是否合法,它需要依赖我们从远程获取数据的服务对RemoteData代码如下:
在工厂的参数中,我们可以直接传入服务对RemoteData,NG的依赖注入机制便帮我们做好了其他工作若。不过一定要保证这个参数的名称与服务名称一致,NG是根据名称来识别的。参数的名次与服务名称不一致,你就必须显示的声明一下,方式如下:
app.factory( '验证',[ '对RemoteData',函数(remoteDataService){
return function(){
如果(remoteDataService.name == 'N'){
警报( '验证通过');
}
};
}]);
我们在控制器中注入服务也是同样的道理,使用的名称需要与服务名称一致才可以正确注入否则,你必须使用$注射来手动指定注入的服务比如。:
function testC(scope,rd){
scope.getData = function(){
alert('name:'+ rd.name +'value:'+ rd.value);
}
}
testC。$ inject = ['$ scope','remoteData'];
在控制器中注入服务,也可以在定义控制器时使用数组作为第二个参数,在此处
把服务注入进去,这样在函数体中使用不一致的服务名称也是可以的,不过要确保注入的顺序是一致的,如:
app.controller( 'TESTC',[ '$范围', '对RemoteData',函数($范围,RD){
$ scope.getData = function(){
alert('name:'+ rd.name +'value:'+ rd.value);
}
}]);
4.7.4自定义服务示例
接下来让我们看下例子(例08自定义服务)代码,自定义userService服务:
demoApp.factory('userService',['$ http',function($ http){
var doGetUser = function(userId,path){
//返回$ http({
//方法:'JSONP',
// url:path
//});
/ *手动指定数据* /
var data = {userId:“woshishui”,userName:“我是谁”,userInfo:“我是谁!我是谁!”} ;;
如果(用户id == '张三'){
data = {userId:“zhangsan”,userName:“张三”,userInfo:“我是张三,我为自己”};
} else if(userId =='lisi'){
data = {userId:“lisi”,userName:“李四”,userInfo:“我是李四,我为卿狂!”};
}
返回数据;
}
返回{
/ * userService对外暴露的函数,可有多个* /
getUser:function(userId){
return doGetUser(userId,'.. / xxxx / xxx.action');
}
};
}]);
我们创建了一个只有一个方法的userService,的getUser为这个服务从后台获取用户信息的函数,并且对外暴露。当然,由于这是一个静态的例子,无法访问后台,那么我们便制定其返回的数据。
然后我们把这个服务添加到我们的控制器中。我们建立一个控制器并加载(或者注入)userService作为运行时依赖,我们把服务的名字作为参数传递给控制器函数:
demoApp.controller(“test8Controller”,function($ scope,userService){
/ *文章信息* /
$ scope.articles = [{
标题:“爱飞像风”,
userId:“zhangsan”,
userName:“张三”
},{
标题:“无法停止的雨”,
userId:“lisi”,
userName:“李四”
}];
$ scope.showUserInfo = false; //显示作者详细信息开关
$ scope.currentUser = {}; //当前选中的作者
$ scope.getUserInfo = function(userId){
$ scope.currentUser = userService.getUser(userId);
//调用 userService的getUser函数
$scope.showUserInfo = true;
setTimeout(function(){//定时器:隐藏作者详细信息
$scope.showUserInfo = false;
},3000);
}
});
我们的userService注入到我们的test8Controller后,我们就可以像使用其他服务(我们前面提到的$http服务)一样的使用userService了。
相关的HTML代码如下:
/* View HTML*/
<tr ng-repeat="article_ in articles">
<td>
{{article_.title}}
</td>
<td>
<a href="javascript:void(0);" ng-click="getUserInfo(article_.userId)"> {{article_.userName}} </a>
</td>
</tr>
......
<div ng-show="showUserInfo">
用户ID:{{currentUser.userId}}<br/>
用户名:{{currentUser.userName}}<br/>
用户简介:{{currentUser.userInfo}}<br/>
</div>
4.8依赖注入DI
通过依赖注入,ng想要推崇一种声明式的开发方式,即当我们需要使用某一模块或服务时,不需要关心此模块内部如何实现,只需声明一下就可以使用了。在多处使用只需进行多次声明,大大提高可复用性。
比如我们的controller,在定义的时候用到一个$scope参数。
app.controller('testC',function($scope){});
如果我们在此处还需操作其他的东西,比如与浏览器地址栏进行交互。我们只需再多添
一个参数$位置进去:
app.controller( 'TESTC',函数($范围,$位置){});
这样便可以通过$位置来与地址栏进行交互了,我们仅仅是声明了一下,所需的其他代码,框架已经帮我们注入了。我们很明显的感觉到了这个函数已经不是常规意义上的的JavaScript函数了,在常规的函数中,把形参换一个名字照样可以运行,但在此处若是把$范围换成别的名字,程序便不能运行了。因为这是已经定义好的服务名称。
这便是依赖注入机制。顺理成章的推断,我们可以自己定义模块和服务,然后在需要的地方进行声明,由框架来替我们注入。
来看下我们如何定义一个服务:
app.factory( 'TPLS',函数(){
return ['tpl1','tpl2','tpl3','tpl4'];
});
看上去相当简单,是因为我在这里仅仅是直接返回一个数组。在实际应用中,这里应该是需要向服务器发起一个请求,来获取到这些模板们。服务的定义方式有好几种,包括使用提供方法,使用工厂方法,使用的服务方法。它们之间的区别暂且不关心。我们现在只要能创建一个服务出来就可以了。我使用了工厂方法。一个需要注意的地方是,框架提供的服务名字都是由$开头的,所以我们自己定义的最好不要用$开头,防止发生命名冲突。
定义好一个服务后,我们就可以在控制器中声明使用了,如下:
app.controller( 'TESTC',函数($范围,TPLS){
$ scope.question = questionModel;
$ scope.nowTime = new Date()。valueOf();
$ scope.templates = tpls; //赋值到$范围中
$ scope.addOption = function(){
var o = {content:''};
$ scope.question.options.push(O);
};
$ scope.delOption = function(index){
$ scope.question.options.splice(指数,1);
};
});
此时,若在模板中书写如下代码,我们便可以获取到服务第三方物流所提供的数据了:
模板:
<a href =“javascript:void(0);” ng-repeat =“t in templates”> {{t}} </a> <br />
4.9路由(路径)
在谈路由机制前有必要先提一下现在比较流行的单页面应用,就是所谓的单页APP。为了实现无刷新的视图切换,我们通常会用ajax请求从后台取数据,然后套上HTML模板渲染在页面上,然而AJAX的一个致命缺点就是导致浏览器后退按钮失效,尽管我们可以在页面上放一个大大的返回按钮,让用户点击返回来导航,但总是无法避免用户习惯性的点后退。解决此问题的一个方法是使用hash,监听hashchange事件来进行视图切换,另一个方法是用HTML5的历史API,通过pushState()记录操作历史,监听popstate事件来进行视图切换,也有人把这叫pjax技术基本流程如下:
如此一来,便形成了通过地址栏进行导航的深度链接(deeplinking),也就是我们所需要的路由机制。通过路由机制,一个单页应用的各个视图就可以很好的组织起来了。
4.9.1 ngRoute内容
ng的路由机制是靠ngRoute提供的,通过hash和history两种方式实现了路由,可以检测浏览器是否支持history来灵活调用相应的方式。ng的路由(ngRoute)是一个单独的模块,包含以下内容:
l 服务$routeProvider用来定义一个路由表,即地址栏与视图模板的映射
l 服务$routeParams保存了地址栏中的参数,例如{id : 1, name : 'tom'}
l 服务$route完成路由匹配,并且提供路由相关的属性访问及事件,如访问当前路由对应的controller
l 指令ngView用来在主视图中指定加载子视图的区域
以上内容再加上$location服务,我们就可以实现一个单页面应用了。下面来看一下具体如何使用这些内容。
4.9.2 ng的路由机制
第一步:引入文件和依赖
ngRoute模块包含在一个单独的文件中,所以第一步需要在页面上引入这个文件,如下:
<script src="http://code.angularjs/1.2.8/angular.min.js"></script>
<script src="http://code.angularjs/1.2.8/angular-route.min.js"></script>
光引入还不够,我们还需在模块声明中注入对ngRoute的依赖,如下:
var app = angular.module('MyApp', ['ngRoute']);
完成了这些,我们就可以在模板或是controller中使用上面的服务和指令了。下面我们需要定义一个路由表。
第二步:定义路由表
$routeProvider提供了定义路由表的服务,它有两个核心方法,when(path,route)和otherwise(params),先看一下核心中的核心when(path,route)方法。
when(path,route)方法接收两个参数,path是一个string类型,表示该条路由规则所匹配的路径,它将与地址栏的内容($location.path)值进行匹配。如果需要匹配参数,可以在path中使用冒号加名称的方式,如:path为/show/:name,如果地址栏是/show/tom,那么参数name和所对应的值tom便会被保存在$routeParams中,像这样:{name : tom}。我们也可以用*进行模糊匹配,如:/show*/:name将匹配/showInfo/tom。
route参数是一个object,用来指定当path匹配后所需的一系列配置项,包括以下内容:
l controller //function或string类型。在当前模板上执行的controller函数,生成新的scope;
l controllerAs //string类型,为controller指定别名;
l template //string或function类型,视图z所用的模板,这部分内容将被ngView引用;
l templateUrl //string或function类型,当视图模板为单独的html文件或是使用了<script type="text/ng-template">定义模板时使用;
l resolve //指定当前controller所依赖的其他模块;
l redirectTo //重定向的地址。
最简单情况,我们定义一个html文件为模板,并初始化一个指定的controller:
function emailRouteConfig($routeProvider){
$routeProvider.when('/show', {
controller:ShowController,
templateUrl:'show.html'
})。
当( '/ PUT /:名称',{
controller:PutController,
templateUrl:'put.html'
});
};
否则(PARAMS)方法对应路径匹配不到时的情况,这时候我们可以配置一个redirectTo参数,让它重定向到404页面或者是首页。
第三步:在主视图模板中指定加载子视图的位置
我们的单页面程序都是局部刷新的,那这个“局部”是哪里呢,这就轮到ngView出马了,只需在模板中简单的使用此指令,在哪里用,哪里就是“局部”。例如:
<div ng-view> </ div>或:<ng-view> </ ng-view>
我们的子视图将会在此处被引入进来。完成这三步后,你的程序的路由就配置好了。
4.9.3路由示例
下面我们将用一个例子(例09)来说明路由的使用方式及步骤:
1.为demoApp添加一个路由,代码如下:
demoApp.config(['$ routeProvider',function($ routeProvider){
$ routeProvider.when('/ list',{
templateUrl:'route / list.html',
controller:'routeListController'
})。when('/ list /:id',{
templateUrl:'route / detail.html',
controller:'routeDetailController'
})。除此以外({
redirectTo:'/ list'
});
}]);
/ list对应为:route / list.html页面,显示用户列表; / list /:id对应于route / detail.html页面,显示用户详细信息。
2.为list.html和detail.html分别声明控制器:routeListController和routeDetailController。
demoApp.controller('routeListController',function($ scope){
$ scope.users = [{userId:“zhangsan”,userName:“张三”,userInfo:“我是张三,我为自己带盐!”},
{用户名: “丽丝”,用户名: “李四”,USERINFO: “我是李四,我为卿狂”},
{用户名: “woshishui”,用户名: “我是谁”,USERINFO: “我是谁我是谁我是谁!!”}];
});
demoApp.controller('routeDetailController',function($ scope,$ routeParams,userService){
$ scope.userDetail = userService.getUser($ routeParams.id);
});
routeDetailController中如上面提到的一样,注入了userService服务,在这里直接拿来用。
3.创建list.html和detail.html页面,代码如下:
<HR />
<h3>路线:List.html(用户列表页面)</ h3>
<UL>
<li ng-repeat =“用户中的用户”>
<a href="#/list/{{ user.userId }}"> {{user.userName}} </a>
</ LI>
</ UL>
<HR />
<h3>路线:detail.html(用户详细信息页面)</ h3>
<h3>用户名:<span style =“color:red;”> {{userDetail.userName}} </ span> </ h3>
<DIV>
<跨度>用户ID:{{userDetail.userId}} </跨度> <跨度>用户名:{{userDetail.userName}} </跨度>
</ DIV>
<DIV>
用户简介:<跨度> {{userDetail.userInfo}} </跨度>
</ DIV>
<DIV>
<a href="#/list">返回</a>
</ DIV>
4.路由局部刷新位置:
<h1> AngularJS路由(路线)示例</ h1>
<div ng-view> </ div>
4.10 NG动画效果
4.10.1 NG动画效果简介
NG动画效果,现在可以通过CSS3或者是JS来实现,如果是通过JS来实现的话,需要其他JS库(比如JQuery的)来支持,实际上底层实现还是靠其他JS库,只是NG将其封装了,
使其更易使用。
NG动画效果包含以下几种:
- 输入:元素添加到DOM中时执行动画;
- 离开:元素从DOM删除时执行动画;
- 移动:移动元素时执行动画;
- beforeAddClass:在给元素添加类之前执行动画;
- addClass:在给元素添加类时执行动画;
- beforeRemoveClass:在给元素删除类之前执行动画;
- removeClass:在给元素删除CLASS时执行动画。
其相关参数为:
var ngModule = angular.module('YourApp',['ngAnimate']);
demoApp.animation('。my-crazy-animation',function(){
返回{
输入:function(element,done){
//在此处运行动画,并在动画完成时调用done
返回功能(已取消){
//动画时会调用此(可选)函数
//完成或取消动画时(取消动画)
如果取消,// flag将被设置为true)。
};
},
离开:function(element,done){},
move:function(element,done){},
//在添加类之前可以触发的动画
beforeAddClass: function(element, className, done) { },
//animation that can be triggered after the class is added
addClass: function(element, className, done) { },
//animation that can be triggered before the class is removed
beforeRemoveClass: function(element, className, done) { },
//animation that can be triggered after the class is removed
removeClass: function(element, className, done) { }
};
});
4.10.2 动画效果示例
下面我们来看下DEMO中的例子(例10)。
1.首先,我们在demoApp下定义一个动画效果,匹配CLASS:” .border-animation”
/*定义动画*/
demoApp.animation('.border-animation', function(){
return{
beforeAddClass : function (element, className, done) {
$(element).stop().animate({
'border-width':1
},2000, function() {
done();
});
},
removeClass : function (element ,className ,done ) {
$(element).stop().animate({
'border-width':50
},3000, function() {
done();
});
}
};
});
动画效果的含义就是:在匹配CLASS为边界的动画的元素添加一个CLASS之前使其边框的宽度在2秒内变为1PX;并在其移除一个CLASS时使其边框的宽度在3秒内变为50像素。
2.视图中的代码如下(主要,其他相关样式请查看例子代码):
<div class =“border-animation”ng-show =“testShow”> </ div>
<a href =“javascript:void(0);” ng-click =“testShow =!testShow”>更改</a>
NG-显示为假时会为其加上“NG隐藏“的CLASS; NG-显示为真时会为其移除“NG隐藏“的CLASS,从而触发动画效果。
3.其他代码:
demoApp.controller(“test10Controller”,function($ scope,$ animate){
$ scope.testShow = true;
});
5功能演示
略(详情请看AngularJS / demo WEB演示)
6 AngularJS进阶
6.1数据绑定原理研究
角用户都想知道数据绑定是怎么实现的你可能会看到各种各样的词汇:?$手表,$申请,$消化,脏检查......它们是什么它们是如何工作的呢?这里我想回答这些问题,其实它们在官方的文档里都已经回答了,但是我还是想把它们结合在一起来讲,但是我只是用一种简单的方法来讲解,如果要想了解技术细节,查看源代码。
6.1.1 AngularJS扩展事件循环
我们的浏览器一直在等待事件,比如用户交互。假如你点击一个按钮或者在输入框里输入东西,事件的回调函数就会在JavaScript的解释器里执行,然后你就可以做任何DOM操作,等回调函数执行完毕时,浏览器就会相应地对DOM做出变化。(记住,这是个重要的概念),为了解释什么是上下文以及它如何工作,我们还需要解释更多的概念。
6.1.2 $ watch队列
每次你绑定一些东西到你的DOM上时你就会往$看队列里插入一条$腕表。想象一下$手表就是那个可以检测它监视的模型里时候有变化的东西。例如你有如下的代码:
/ *查看index.html * /
用户:<input type =“text”ng-model =“user”/>
密码:<input type =“password”ng-model =“pass”/>
在这里我们有个$ scope.user,他被绑定在了第一个输入框上,还有个$ scope.pass,它被绑定在了第二个输入框上,然后我们在$ watch list里面加入两个$手表。
再看下面的例子:
/ * Controller controllers.js * /
app.controller('MainCtrl',function($ scope){
$ scope.foo =“Foo”;
$ scope.world =“世界”;
});
/ *查看index.html * /
你好,世界 }}
这里,即便我们在$范围上添加了两个东西,但是只有一个绑定在了DOM上,因此在这里只生成了一个$手表。
再看下面的例子:
/ * Controller controllers.js * /
app.controller('MainCtrl',function($ scope){
$ scope.people = [...];
});
/ *查看index.html * /
<UL>
<li ng-repeat =“人在人”>
{{person.name}} - {{person.age}}
</li>
</ul>
这里又生成了多少个$watch呢?每个person有两个(一个name,一个age),然后ng-repeat又有一个,因此10个person一共是(2 * 10) +1,也就是说有21个$watch。
因此,每一个绑定到了DOM上的数据都会生成一个$watch。
那这写$watch是什么时候生成的呢?
当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段),Angular解释器会寻找每个directive,然后生成每个需要的$watch。
6.1.3 $digest循环
还记得我前面提到的扩展的事件循环吗?当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发。这个循环是由两个更小的循环组合起来的。一个处理evalAsync队列,另一个处理$watch队列。 这个是处理什么的呢?$digest将会遍历我们的$watch,然后询问:
•嘿,$watch,你的值是什么?
◦是9。
•好的,它改变过吗?
◦没有,先生。
•(这个变量没变过,那下一个)
•你呢,你的值是多少?
◦报告,是Foo。
•刚才改变过没?
◦改变过,刚才是Bar。
•(很好,我们有DOM需要更新了)
•继续询问直到$watch队列都检查过。
这就是所谓的dirty-checking。既然所有的$watch都检查完了,那就要问了:有没有$watch更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。记住如果循环超过10次的话,它将会抛出一个异常,防止无限循环。当$digest循环结束时,DOM相应地变化。
例如:
/*Controller controllers.js */
app.controller('MainCtrl',function(){
$ scope.name =“Foo”;
$ scope.changeFoo = function(){
$ scope.name =“Bar”;
}
});
/ *查看index.html * /
{{ 名称 }}
<button ng-click =“changeFoo()”>更改名称</ button>
这里我们有一个$手表因为NG单击不生成$腕表(函数是不会变的)。
我们可以看出NG的处理流程:
•我们按下按钮;
•浏览器接收到一个事件,进入角度上下文;
•$消化循环开始执行,查询每个$表是否变化;
•由于监视$ scope.name的$腕表报告了变化,它会强制再执行一次$消化循环;
•新的$消化循环没有检测到变化;
•浏览器拿回控制权,更新与$ scope.name新值相应部分的DOM。
这里很重要的是每一个进入角度上下文的事件都会执行一个$ digest循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$ watch。
6.1.4如何进入角度上下文
谁决定什么事件进入角度上下文,而哪些又不进入呢?通过$ apply!
如果当事件触发时,你调用$ apply,它会进入角度上下文,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用$ apply啊,为什么?Angular已经做了!因此你点击带有NG点击的元素时,时间就会被封装到一个$应用调用。如果你有一个NG-模型= “富” 的输入框,然后你敲一个楼事件就会这样调用$ apply(“foo ='f';”)。
角什么时候不会自动为我们$申请呢?
这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$ apply,事件没有进入angular context,$ digest循环永远没有执行。
我们来看一个有趣的例子:
假设我们有下面这个指令和控制。
/ *控制器app.js * /
app.directive('clickable',function(){
返回{
限制:“E”,
范围: {
foo:'=',
吧:'='
},
模板:'<ul style =“<li> {{foo}} </ li> <li> {{bar}} </ li> </ ul>',
link:function(scope,element,attrs){
element.bind('click',function(){
scope.foo ++;
scope.bar ++;
});
}
}
});
app.controller('MainCtrl',function($ scope){
$ scope.foo = 0;
$ scope.bar = 0;
});
它将FOO和酒吧从控制器里绑定到一个列表里面,每次点击这个元素的时候,富和酒吧都会自增1,那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到$应用里面的常见的事件,这意味着我们会失去我们的计数吗?不会。
真正的结果是:$ scope确实改变了,但是没有强制$ digest循环,监视foo和bar的$ watch没有执行。也就是说如果我们自己执行一次$ apply那么这些$ watch就会看见这些变化,然后根据需要更新DOM。
执行$适用:
element.bind('click',function(){
scope.foo ++;
scope.bar ++;
。范围$适用();
});
$应用是我们的$范围(或者是direcvie里的链接函数中的范围)的一个函数,调用它会强制一次$消化循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行$适用的标志)。
更好的使用$适用的方法:
element.bind('click',function(){
scope。$ apply(function(){
scope.foo ++;
scope.bar ++;
});
})
有什么不一样的?差别就是在第一个版本中,我们是在angular context的外面更新的数据,如果有发生错误,Angular永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个警示框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进$应用里面,角永远不会知道失败了,警报框就永远不会弹出来了。
因此,如果你想使用一个jQuery的插件,并且要执行$消化循环来更新你的DOM的话,要确保你调用了$适用。
有时候我想多说一句的是有些人在不得不调用$申请时会“感觉不妙”,因为他们会觉得他们做错了什么。其实不是这样的,角不是什么魔术师,他也不知道第三方库想要更新绑定的数据。
6.1.5使用$表来监视
你已经知道了我们设置的任何绑定都有一个它自己的$手表,当需要时更新DOM,但是我们如果要自定义自己的手表呢简单,来看个例子?
/ *控制器app.js * /
app.controller('MainCtrl',function($ scope){
$ scope.name =“Angular”;
$ scope.updated = -1;
$ scope。$ watch('name',function(){
$ scope.updated ++;
});
});
/ *查看index.html * /
<body ng-controller =“MainCtrl”>
<input ng-model =“name”/>
名称已更新:{{updated}}次。
</ BODY>
这就是我们创造一个新的$表的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,在这里,$ scope.name(注意我们只需要
用名)。第二个参数是当$看说我监视的表达式发生变化后要执行的。我们要知道的第一件事就是当控制器执行到这个$表时,它会立即执行一次,因此我们设置更新为-1。
例子2:
/ *控制器app.js * /
app.controller('MainCtrl',function($ scope){
$ scope.name =“Angular”;
$ scope.updated = 0;
$ scope。$ watch('name',function(newValue,oldValue){
if(newValue === oldValue){return; } // AKA首次运行
$ scope.updated ++;
});
});
/ *查看index.html * /
<body ng-controller =“MainCtrl”>
<input ng-model =“name”/>
名称已更新:{{updated}}次。
</ BODY>
看的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。
例子3:
/ *控制器app.js * /
app.controller('MainCtrl',function($ scope){
$ scope.user = {name:“Fox”};
$ scope.updated = 0;
$ scope。$ watch('user',function(newValue,oldValue){
if(newValue === oldValue){return; }
$ scope.updated ++;
});
});
/ *查看index.html * /
<body ng-controller =“MainCtrl”>
<input ng-model =“user.name”/>
名称已更新:{{updated}}次。
</ BODY>
我们想要监视$ scope.user对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。
呃?没用,为啥?因为$观看默认是比较两个对象所引用的是否相同,在例子1和2里面,每次更改$ scope.name都会创建一个新的基本变量,因此$手表会执行,因为对这个变量的引用已经改变了。在上面的例子里,我们在监视$ scope.user,当我们改变$ scope.user.name时,对$ scope.user的引用是不会改变的,我们只是每次创建了一个新的$ scope.user.name,但是$ scope.user永远是一样的。
例子4:
/ *控制器app.js * /
app.controller('MainCtrl',function($ scope){
$ scope.user = {name:“Fox”};
$ scope.updated = 0;
$ scope。$ watch('user',function(newValue,oldValue){
if(newValue === oldValue){return; }
$ scope.updated ++;
,真实);
});
/ *查看index.html * /
<body ng-controller =“MainCtrl”>
<input ng-model =“user.name”/>
名称已更新:{{updated}}次。
</ BODY>
现在有用了吧!因为我们对$手表加入了第三个参数,它是一个布尔类型的参数,表示的是我们比较的是对象的值而不是引用。由于当我们更新$ scope.user.name时$ scope.user也会改变,所以能够正确触发。
6.1.6总结
我希望你们已经学会了在角中数据绑定是如何工作的。我猜想你的第一印象是脏检查很慢,好吧,其实是不对的。它像闪电般快。但是,如果你在一个模版里有2000-3000个手表,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了。
无论如何,随着ECMAScript6的到来,在折角未来的版本里我们将会有Object.observe那样会极大改善消化$循环的速度。
6.2自定义指令详解
角度的指令机制.angular通过指令的方式实现了HTML的扩展,增强后的HTML不仅长相焕然一新,同时也获得了很多强大的技能。更厉害的是,你还可以自定义指令,这就意味着HTML标签的范围可以扩展到无穷大.angular赋予了你造物主的能力。既然是作为角度的精华之一,相应的指令相关的知识也很多的。
6.2.1指令的编译过程
在开始自定义指令之前,我们有必要了解一下指令在框架中的执行流程:
1.浏览器得到HTML字符串内容,解析得到DOM结构。
2.ng引入,把DOM结构扔给$ compile函数处理:
①找出DOM结构中有变量占位符;
②匹配找出DOM中包含的所有指令引用;
③把指令关联到DOM;
④关联到DOM的多个指令按权重排列;
⑤执行指令中的编译函数(改变DOM结构,返回链接函数);
⑥得到的所有链接函数组成一个列表作为$ compile函数的返回。
3.执行link函数(连接模板的范围)。
这里注意区别一下$编译和编译,前者是NG内部的编译服务,后者是指令中的编译函数,两者发挥作用的范围不同pile和链接函数息息相关又有所区别,这个在后面会讲。了解执行流程对后面的理解会有帮助。
在这里有些人可能会问,角不就是一个JS框架吗,怎么还能跟编译扯上呢,又不是像C ++那样的高级语言。其实此编译非彼编译,NG编译的工作是解析指令,绑定监听器,替换模板中的变量等。因为工作方式很像高级语言编辑中的递归,堆栈过程,所以起名为编译,不要疑惑。
6.2.2指令的使用方式及命名方法
指令的几种使用方式如下:
- 作为标签:<我-DIR> </我的-DIR>
- 作为属性:<span my-dir =“exp”> </ span>
- 作为注释:<! - directive:my-dir exp - >
- 作为类名:<span class =“my-dir:exp;”> </ span>
其实常用的就是作为标签和属性,下面两种用法目前还没见过,感觉就是用来卖萌的,姑且留个印象。我们自定义的指令就是要支持这样的用法。
关于自定义指令的命名,你可以随便怎么起名字都行,官方是推荐用[命名空间 - 指令名称]。这样的方式,像纳克控制器不过你可千万不要用NG-前缀了,防止与。系统自带的指令重名另外一个需知道的地方,指令命名时用驼峰规则,使用时用 - 分割各单词如:定义myDirective,使用时像这样:<我的指导性>。
6.2.3自定义指令的配置参数
下面是定义一个标准指令的示例,可配置的参数包括以下部分:
myModule.directive('namespaceDirectiveName',function factory(injectables){
var directiveDefinitionObject = {
restrict:string,//指令的使用方式,包括标签,属性,类,注释
priority:number,//指令执行的优先级
template: string,//指令使用的模板,用HTML字符串的形式表示
templateUrl: string,//从指定的url地址加载模板
replace: bool,//是否用模板替换当前元素,若为false,则append在当前元素上
transclude: bool,//是否将当前元素的内容转移到模板中
scope: bool or object,//指定指令的作用域
controller: function controllerConstructor($scope, $element, $attrs, $transclude){...},//定义与其他指令进行交互的接口函数
require: string,//指定需要依赖的其他指令
link: function postLink(scope, iElement, iAttrs) {...},//以编程的方式操作DOM,包
括添加监听器等
compile: function compile(tElement, tAttrs, transclude){
return: {
pre: function preLink(scope, iElement, iAttrs, controller){...},
post: function postLink(scope, iElement, iAttrs, controller){...}
}
}//编程的方式修改DOM模板的副本,可以返回链接函数
};
return directiveDefinitionObject;
});
看上去好复杂的样子,定义一个指令需要这么多步骤嘛?当然不是,你可以根据自己的需要来选择使用哪些参数。事实上优先和编译用的比较少,模板和templateUrl又是互斥的,两者选其一即可所以不必紧张,接下来分别学习一下这些参数:
l指令的表现配置参数:restrict,template,templateUrl,replace,transclude;
l指令的行为配置参数:compile和link;
l指令划分作用域配置参数:scope;
l指令间通信配置参数:controller和require。
6.2.3指令的表现参数限制等
指令的表现配置参数:限制,模板,templateUrl,更换,transclude。
我将先从一个简单的例子开始。
例子的代码如下:
var app = angular.module('MyApp',[],function(){console.log('here')});
app.directive( '的sayHello',函数(){
返回{
限制:'E',
模板:'<div>你好</ div>'
};
})
然后在页面中,我们就可以使用这个名为sayHello的的指令了,它的作用就是输出一个招呼单词像这样使用:
<发言权问候> </说问候>
这样页面就会显示出你好了,看一下生成的代码:
<说问候>
<DIV>你好</ DIV>
</说问候>
稍稍解释一下我们用到的两个参数,restirct用来指定指令的使用类型,其取值及含义如下:
取值 | 含义 | 使用示例 |
Ë | 标签 | <my-menu title = Products> </ my-menu> |
一个 | 属性 | <div my-menu = Products> </ div> |
C | 类 | <div class =“my-menu”:Products> </ div> |
中号 | 注释 | <! - directive:my-menu Products - > |
默认值是A.也可以使用这些值的组合,如EA,EC等等我们这里指定为E,那么它就可以像标签一样使用了如果指定为A,我们使用起来应该像这样。:
<div say-hello> </ div>
从生成的代码中,你也看到了模板的作用,它就是描述你的指令长什么样子,这部分内容将出现在页面中,即该指令所在的模板中,既然是模板中,模板的内容中也可以使用NG-MODLE等其他指令,就像在模板中使用一样。
在上面生成的代码中,我们看到了<DIV>你好</ DIV>外面还包着一层<说问候>标签,如果我们不想要这一层多余的东西了,则更换就派上用场了,在配置中将取代赋值为真,将得到如下结构:
<DIV>你好</ DIV>
替代的作用正如其名,将指令标签替换为了寺庙中定义的内容。不写的话默认为假。
上面的模板未免也太简单了,如果你的模板HTML较复杂,如自定义一个UI组件指令,难道要拼接老长的字符串?当然不需要,此时只需用templateUrl便可解决问题。你可以将指令的模板单独命名为一个HTML文件,然后在指令定义中使用templateUrl指定好文件的路径即可,如:
templateUrl:'helloTemplate.html'
系统会自动发一个http请求来获取到对应的模板内容。是不是很方便呢,你不用纠结于拼接字符串的烦恼了。如果你是一个追求完美的有考虑性能的工程师,可能会发问:那这样的话岂不是要牺牲一个http请求?这也不用担心,因为ng的模板还可以用另外一种方式定义,那就是使用<script>标签。使用起来如下:
<script type="text/ng-template" id="helloTemplate.html">
<div>hello</div>
</script>
你可以把这段代码写在页面头部,这样就不必去请求它了。在实际项目中,你也可以将所有的模板内容集中在一个文件中,只加载一次,然后根据id来取用。
接下来我们来看另一个比较有用的配置:transclude,定义是否将当前元素的内容转移到模板中。看解释有点抽象,不过亲手试试就很清楚了,看下面的代码(例06):
app.directive('sayHello',function(){
return {
restrict : 'E',
template : '<div>hello,<b ng-transclude></b>!</div>',
replace : true,
transclude : true
};
})
指定了transclude为true,并且template修改了一下,加了一个<b>标签,并在上面使用了ng-transclude指令,用来告诉指令把内容转移到的位置。那我们要转移的内容是什么呢?请看使用指令时的变化:
<say-hello>美女</say-hello>
内容是什么你也看到了哈~在运行的时候,美女将会被转移到<b>标签中,原来此配置的作用就是——乾坤大挪移!看效果:
hello, 美女!
这个还是很有用的,因为你定义的指令不可能老是那么简单,只有一个空标签。当你需要对指令中的内容进行处理时,此参数便大有可用。
6.2.4指令的行为参数:compile和link
6.2.3中简单介绍了自定义一个指令的几个简单参数,restrict、template、templateUrl、replace、transclude,这几个理解起来相对容易很多,因为它们只涉及到了表现,而没有涉及行为。我们继续学习ng自定义指令的几个重量级参数:compile和link
l 理解compile和link
不知大家有没有这样的感觉,自己定义指令的时候跟写jQuery插件有几分相似之处,都是先预先定义好页面结构及监听函数,然后在某个元素上调用一下,该元素便拥有了特殊的功能。区别在于,jQuery的侧重点是DOM操作,而ng的指令中除了可以进行DOM操作外,更注重的是数据和模板的绑定。jQuery插件在调用的时候才开始初始化,而ng指令在页面加载进来的时候就被编译服务($compile)初始化好了。
在指令定义对象中,有compile和link两个参数,它们是做什么的呢?从字面意义上看,编译、链接,貌似太抽象了点。其实可大有内涵,为了在自定义指令的时候能正确使用它们,现在有必要了解一下ng是如何编译指令的。
l 指令的解析流程详解
我们知道ng框架会在页面载入完毕的时候,根据ng-app划定的作用域来调用$compile服务进行编译,这个$compile就像一个大总管一样,清点作用域内的DOM元素,看看哪些元素上使用了指令(如<div ng-modle=”m”></div>),或者哪些元素本身就是个指令(如<mydierc></mydirec>),或者使用了插值指令( {{}}也是一种指令,叫interpolation directive),$compile大总管会把清点好的财产做一个清单,然后根据这些指令的优先级(priority)排列一下,真是个细心的大总管哈~大总管还会根据指令中的配置参数(template,place,transclude等)转换DOM,让指令“初具人形”。
然后就开始按顺序执行各指令的compile函数,注意此处的compile可不是大总管$compile,人家带着$是土豪,此处执行的compile函数是我们指令中配置的,compile函数中可以访问到DOM节点并进行操作,其主要职责就是进行DOM转换,每个compile函数执行完后都会返回一个link函数,这些link函数会被大总管汇合一下组合成一个合体后的link函数,为了好理解,我们可以把它想象成葫芦小金刚,就像是进行了这样的处理。
//合体后的link函数
function AB(){
A(); //子link函数
B(); //子link函数
}
接下来进入link阶段,合体后的link函数被执行。所谓的链接,就是把view和scope链接起来。链接成啥样呢?就是我们熟悉的数据绑定,通过在DOM上注册监听器来动态修改scope中的数据,或者是使用$watchs监听 scope中的变量来修改DOM,从而建立双向绑定。由此也可以断定,葫芦小金刚可以访问到scope和DOM节点。
不要忘了我们在定义指令中还配置着一个link参数呢,这么多link千万别搞混了。那这
个链接函数是干嘛的呢,我们不是有葫芦小金刚了嘛?那我告诉你,其实它是一个小三。此话怎讲?编译函数执行后返回的链接函数,但若没有配置编译函数呢?葫芦小金刚自然就不存在了。
正房不在了,当然就轮到小三出马了,大总管$编译就把这里的链接函数拿来执行。这就意味着,配置的链接函数也可以访问到的范围以及DOM节点。值得注意的是,编译函数通常是不会被配置的,因为我们定义一个指令的时候,大部分情况不会通过编程的方式进行DOM操作,而更多的是进行监听器的注册,数据的绑定。所以,小三名正言顺的被大总管宠爱。
听完了大总管,葫芦小金刚和小三的故事,你是不是对指令的解析过程比较清晰了呢?不过细细推敲,你可能还是会觉得情节生硬,有些细节似乎还是没有透彻的明白,所以还需要再理解下面的知识点:
l compile和link的区别
其实在我看完官方文档后就一直有疑问,为什么监听器,数据绑定不能放在编译函数中,而偏偏要放在链接函数中?为什么有了编译还需要链接?就跟你质疑我编的故事一样,为什么最后小三被宠爱了?所以我们有必要探究一下,编译和链接之间到底有什么区别。好,正房与小三的PK现在开始。
首先是性能举个例子:
<UL>
<li ng-repeat =“数组中的a”>
<input ng-modle =“am”/>
</ LI>
</ UL>
我们的观察目标是NG-重复指令。假设一个前提是不存在的链接。大总管$编译在编译这段代码时,会查找到NG-重复,然后执行它的编译函数,编译函数根据数组的长度复制出ñ个<LI>标签。而复制出的<LI>节点中还有<输入>节点并且使用了NG-MODLE指令,所以编译还要扫描它并匹配指令,然后绑定监听器。每次循环都做如此多的工作。而更加糟糕的一点是,我们会在程序中向阵列中添加元素,此时页面上会实时更新DOM,每次有新元素进来,编译函数都把上面的步骤再走一遍,岂不是要累死了,这样性能必然不行。
现在扔掉那个假设,在编译的时候编译就只管生成DOM的事,碰到需要绑定监听器的地方先存着,有几个存几个,最后把它们汇总成一个链接函数,然后一并执行。这样就轻松多了,编译只需要执行一次,性能自然提升。
另外一个区别是能力。
尽管编译和链接所做的事情差不多,但它们的能力范围还是不一样的。比如正房能管你的存款,小三就不能。小三能给你初恋的感觉,正房却不能。
我们需要看一下编译函数和链接函数的定义:
函数compile(tElement,tAttrs,transclude){...}
功能链接(范围,iElement,iAttrs,控制器){...}
这些参数都是通过依赖注入而得到的,可以按需声明使用。从名字也容易看出,两个函数各自的职责是什么,编译可以拿到transclude,允许你自己编程管理乾坤大挪移的行为。而链接中可以拿到范围和控制器,可以与范围进行数据绑定,与其他指令进行通信。两者虽然都可以拿到元件,但是还是有区别的,看到各自的前缀了吧?编译拿到的是编译前的,是从模板里拿过来的,而链接拿到的是编译后的,已经与作用域建立了
关联,这也正是链路中可以进行数据绑定的原因。
我暂时只能理解到这个程度了实在不想理解这些知识的话,只要简单记住一个原则就行了。如果指令只进行DOM的修改,不进行数据绑定,那么配置在编译函数中,如果指令要进行数据绑定,那么配置在链接函数中。
6.2.5指令的划分作用域参数:范围
我们在上面写了一个简单的<说问候> </说问候>,能够跟美女打招呼但是看看人家纳克内置的指令,都是这么用的:NG-模型=” M”,NG- repeat =“a in array”,不单单是作为属性,还可以赋值给它,与作用域中的一个变量绑定好,内容就可以动态变化了。假如我们的sayHello可以这样用:<say-hello说话=”内容”>美女</说问候>,把要对美女说的话写在一个变量的内容中,然后只要在控制器中修改内容的值,页面就可以显示对美女说的不同的话。这样就灵活多了,不至于见了美女只会说一句招呼,然后就没有然后。
为了实现这样的功能,我们需要使用范围参数,下面来介绍一下。
使用范围为指令划分作用域
顾名思义,范围肯定是跟作用域有关的一个参数,它的作用是描述指令与父作用域的关系,这个父作用域是指什么呢想象一下我们使用指令的场景,页面结构应该是这个样子?
<div ng-controller =“testC”>
<say-hello speak =“content”>美女</ say-hello>
</ DIV>
外层肯定会有一个控制器,而在控制器的定义中大体是这个样子:
var app = angular.module('MyApp',[],function(){console.log('here')});
app.controller( 'TESTC',函数($范围){
$ scope.content ='今天天气真好!';
});
所谓的sayHello的父作用域就是这个名叫TESTC的控制器所管辖的范围,指令与父作用域的关系可以有如下取值:
取值 | 说明 |
假 | 默认值。使用父作用域作为自己的作用域 |
真正 | 新建一个作用域,该作用域继承父作用域 |
JavaScript的对象 | 与父作用域隔离,并指定可以从父作用域访问的变量 |
乍一看取值为假和真好像没什么区别,因为取值为真时会继承父作用域,即父作用域中的任何变量都可以访问到,效果跟直接使用父作用域差不多。但细细一想还是有区别的,有了自己的作用域后就可以在里面定义自己的东西,与跟父作用域混在一起是有本质上的区别。好比是父亲的钱你想花多少花多少,可你自己挣的钱父亲能花多少就不好说了。你若想看这两个作用域的区别,可以在连接函数中打印出来看看,还记得链接函数中可以访问到的范围吧。
最有用的还是取值为第三种,一个对象,可以用键值来显式的指明要从父作用域中使用属性的方式。当范围值为一个对象时,我们便建立了一个与父层隔离的作用域,不过也不是完全隔离,我们可以手工搭一座桥梁,并放行某些参数我们要实现对美女说各种话就得靠这个使用起来像这样。:
范围: {
attributeName1:'BINDING_STRATEGY',
attributeName2:'BINDING_STRATEGY',...
}
键为属性名称,值为绑定策略。等等!啥叫绑定策略?最讨厌冒新名词却不解释的行为!别急,听我慢慢道来。
先说属性名称吧,你是不是认为这个ATTRIBUTENAME1就是父作用域中的某个变量名称?错!其实这个属性名称是指令自己的模板中要使用的一个名称,并不对应父作用域中的变量,稍后的例子中我们来说明再来看绑定策略,它的取值按照如下的规则:
符号 | 说明 | 举例 |
@ | 传递一个字符串作为属性的值 | str:'@string' |
= | 使用父作用域中的一个属性,绑定数据到指令的属性中 | 名称:'=用户名' |
& | 使用父作用域中的一个函数,可以在指令中调用 | getName:'&getUserName' |
总之就是用符号前缀来说明如何为指令传值。你肯定迫不及待要看例子了,我们结合例子看一下,小二,上栗子〜
举例说明
我想要实现上面想像的跟美女多说点话的功能,即我们给sayHello指令加一个属性,通过给属性赋值来动态改变说话的内容主要代码如下:
app.controller( 'TESTC',函数($范围){
$ scope.content ='今天天气真好!';
});
app.directive( '的sayHello',函数(){
返回{
限制:'E',
模板:'<div>你好,<b ng-transclude> </ b>,{{cont}} </ div>',
替换:true,
转录:是的,
范围 : {
cont:'=发言'
}
};
});
然后在模板中,我们如下使用指令:
<div ng-controller =“testC”>
<say-hello speak =“content”>美女</ say-hello>
</ DIV>
看看运行效果:
美女今天天气真好!
执行的流程是这样的:
①指令被编译的时候会扫描到模板中的{{cont}},发现是一个表达式;
②查找范围中的规则:通过说与父作用域绑定,方式是传递父作用域中的属性;
③说与父作用域中的内容属性绑定,找到它的值“今天天气真好!”;
④将内容的值显示在模板中。
这样我们说话的内容的内容就跟父作用域绑定到了一其,如果动态修改父作用域的内容的值,页面上的内容就会跟着改变,正如你点击“换句话”所看到的一样。
这个例子也太小儿科了吧简单虽简单,但可以让我们理解清楚,为了检验你是不是真的明白了,可以思考一下如何修改指令定义,能让sayHello的以如下两种方式使用!
<span say-hello speak =“content”>美女</ span>
<span say-hello =“content”>美女</ span>
答案我就不说了,简单的很。下面有更重要的事情要做,我们说好了要写一个真正能用的东西来着。接下来就结合所学到的东西来写一个折叠菜单,即点击可展开,再点击一次就收缩回去的菜单。
控制器及指令的代码如下(例07):
app.controller( 'TESTC',函数($范围){
$ scope.title ='个人简介';
$ scope.text ='大家好,我是一名前端工程师,我正在研究AngularJs,欢迎大家与我交流';
});
app.directive( '膨胀',函数(){
返回{
限制:'E',
templateUrl:'expanderTemp.html',
替换:true,
转录:是的,
范围 : {
mytitle:'= etitle'
},
link:function(scope,element,attris){
scope.showText = false;
scope.toggleText = function(){
scope.showText =!scope.showText;
}
}
};
});
HTML中的代码如下:
<script type =“text / ng-template”id =“expandderTemp.html”>
<div class =“mybox”>
<div class =“mytitle”ng-click =“toggleText()”>
{{mytitle}}
</ DIV>
<div ng-transclude ng-show =“showText”>
</ DIV>
</ DIV>
</ SCRIPT>
<div ng-controller =“testC”>
<expander etitle =“title”> {{text}} </ expandder>
</ DIV>
还是比较容易看懂的,我只做一点必要的解释。首先我们定义模板的时候使用了ng的一种定义方式<script type =“text / ng-template”id =“expanderTemp.html”>,在指令中就可以用templateUrl根据这个ID来找到模板。指令中的{{mytitle}}表达式由范围参数指定从etitle传递,etitle指向了父作用域中的标题。为了实现点击标题能够展开收缩内容,我们把这部分逻辑放在了link函数中,link函数可以访问到指令的作用域,我们定义showText属性来表示内容部分的显隐,定义toggleText函数来进行控制,然后在模板中绑定好。如果把showText和toggleText定义在控制器中,作为$范围的属性呢?显然是不行的,这就是隔离作用域的意义所在,父作用域中的东西除了标题之外通通被屏蔽。
上面的例子中,范围参数使用了=号来指定获取属性的类型为父作用域的属性,如果我们想在指令中使用父作用域中的函数,使用&符号即可,是同样的原理。
6.2.6指令间通信参数:控制器和要求
使用指令来定义一个用户界面组件是个不错的想法,首先使用起来方便,只需要一个标签或者属性就可以了,其次是可复用性高,通过控制器可以动态控制UI组件的内容,而且拥有双向绑定的能力。当我们想做的组件稍微复杂一点,就不是一个指令可以搞定的了,就需要指令与指令的协作才可以完成,这就需要进行指令间通信。
想一下我们进行模块化开发的时候的原理,一个模块暴露(出口)对外的接口,另外一个模块引用(要求)它,便可以使用它所提供的服务了.ng的指令间协作也是这个原理,这也正是自定义指令时控制器参数和要求参数的作用。
控制器参数用于定义指令对外提供的接口,它的写法如下:
controller:function controllerConstructor($ scope,$ element,$ attrs,$ transclude)
它是一个构造器函数,将来可以构造出一个实例传给引用它的指令。为什么叫控制器(控制器)呢?其实就是告诉引用它的指令,你可以控制我。至于可以控制那些东西呢,就需要在函数体中进行定义了。先看控制器可以使用的参数,作用域,节点,节点的属性,节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数。关于如何对外暴露接口,我们在下面的例子来说明。
需要参数便是用来指明需要依赖的其他指令,它的值是一个字符串,就是所依赖的指令的名字,这样框架就能按照你指定的名字来从对应的指令上面寻找定义好的控制器了。不过还稍稍有点特别的地方,为了让框架寻找的时候更轻松些,我们可以在名字前面加个小小的前缀:^,表示从父节点上寻找,使用起来像这样:require:'^ directiveName “,如果不加,$编译服务只会从节点本身寻找另外还可以使用前缀:?,此前缀将告诉$编译服务,如果所需的控制器没找到,不要抛出异常。
所需要了解的知识点就这些,接下来是例子时间,依旧是从书上抄来的一个例子,我们要做的是一个手风琴菜单,就是多个折叠菜单并列在一起,此例子用来展示指令间的通信再合适不过。
首先我们需要定义外层的一个结构,起名为手风琴,代码如下:
app.directive( '手风琴',函数(){
返回{
限制:'E',
模板:'<div ng-transclude> </ div>',
替换:true,
转录:是的,
controller:function(){
var expanders = [];
this.gotOpended = function(selectedExpander){
angular.forEach(扩展器,函数(E){
if(selectedExpander!= e){
e.showText = false;
}
});
}
this.addExpander = function(e){
expanders.push(E);
}
}
}
});
需要解释的只有控制器中的代码,我们定义了一个折叠菜单数组扩展器,并且通过这个关键字来对外暴露接口,提供两个方法.gotOpended接受一个selectExpander参数用来修改数组中对应扩展器的showText属性值,从而实现对各个子菜单的显隐控制.addExpander方法对外提供向扩展数组增加元素的接口,这样在子菜单的指令中,便可以调用它把自身加入到手风琴中。
看一下我们的扩展需要做怎样的修改呢:
app.directive( '膨胀',函数(){
返回{
限制:'E',
templateUrl:'expanderTemp.html',
替换:true,
转录:是的,
要求:'^?accordion',
范围 : {
标题:'= etitle'
},
link:function(scope,element,attris,accordionController){
scope.showText = false;
accordionController.addExpander(范围);
scope.toggleText = function(){
scope.showText =!scope.showText;
accordionController.gotOpended(范围);
}
}
};
});
首先使用需要参数引入所需的手风琴指令,添加?^前缀表示从父节点查找并且失败后不抛出异常。然后便可以在链接函数中使用已经注入好的accordionController了,调用addExpander方法将自己的作用域作为参数传入,以供accordionController访问其属性。然
后在toggleText方法中,除了要把自己的showText修改以外,还要调用accordionController的gotOpended方法通知父层指令把其他菜单给收缩起来。
指令定义好后,我们就可以使用了,使用起来如下:
<手风琴>
<expander ng-repeat =“扩展器中的扩展器”etitle =“expander.title”>
{{expander.text}}
</膨胀>
</手风琴>
外层使用了手风琴指令,内层使用扩展指令,并且在扩展上用NG-重复循环输出子菜单。请注意这里遍历的数组扩展可不是手风琴中定义的那个扩展,如果你这么认为了,说明还是。对作用域不够了解此膨胀器是NG-重复的值,它是在外层控制器中的,所以,在TESTC中,我们需要添加如下数据:
$ scope.expanders = [
{title:'个人简介',
text:'大家好,我是一名前端工程师,我正在研究AngularJs,欢迎大家与我交流'},
{title:'我的爱好',
文字:'LOL'},
{title:'性格',
文字:'我的性格就是无性格'}
]。
6.3性能及调优
6.3.1性能测试
AnglarJS作为一款优秀的网络框架,可大大简化前端开发的负担。
AnglarJS很棒,但当处理包含复杂数据结构的大型列表时,其运行速度就会非常慢。
这是我们将核心管理页面迁移到AngularJS过程中遇到的问题。这些页面在显示500行数据时本应该工作顺畅,但首个方法的渲染时间竟花费了7秒,太可怕了。后来,我们发现了在实现过程中存在两个主要性能问题。一个与“ng-repeat”指令有关,另一个与过滤器有关。
AngularJS中的ng-repeat在处理大型列表时,速度为什么会变慢?
AngularJS中的ng-repeat在处理2500个以上的双向数据绑定时速度会变慢。这是由于AngularJS通过“脏检查”函数来检测变化。每次检测都会花费时间,所以包含复杂数据结构的大型列表将降低你应用的运行速度。
提高性能的先决条件
时间记录指令
为了测量一个列表渲染所花费的时间,我们写了一个简单的程序,通过使用“NG重复”的属性“$最后”来记录时间。时间存放在TimeTracker服务中,这样时间记录就与服务器端的数据加载分开了。
// Post repeat指令用于记录渲染时间
angular.module( 'siApp.services')指令( 'postRepeatDirective',
['$ timeout','$ log','TimeTracker',
function($ timeout,$ log,TimeTracker){
return函数(范围,元素,attrs){
if(scope。$ last){
$超时(函数(){
var timeFinishedLoadingList = TimeTracker.reviewListLoaded();
var ref = new Date(timeFinishedLoadingList);
var end = new Date();
$ log.debug(“## DOM渲染列表:”+(end - ref)+“ms”);
});
}
};
}
]);
//在HTML中使用:
<tr ng-repeat =“item in items”post-repeat-directive> ... </ tr>
Chrome的开发者工具的时间轴(时间轴)属性
在Chrome的开发者工具的时间轴标签中,你可以看见事件,每秒内浏览器帧数和内存分配。“记忆”工具用来检测内存泄漏,及页面所需的内存。当帧速率每秒低于30帧时就会出现页面闪烁问题。“帧”工具可帮助了解渲染性能,还可显示出一个JavaScript的任务所花费的CPU时间。
通过限制列表的大小进行基本的调优
缓解该问题,最好的办法是限制所显示列表的大小。可通过分页,添加无限滚动条来实现。
分页,我们可以使用AngularJS的“limitTo”过滤器(AngularJS1.1.4版本以后)和“startFrom”过滤器。可以通过限制显示列表的大小来减少渲染时间。这是减少渲染时间最高效的方法。
6.3.2七大调优法则
1.渲染没有数据绑定的列表
这是最明显的解决方案,因为数据绑定是性能问题最可能的根源。如果你只想显示一次列表,并不需要更新,改变数据,放弃数据绑定是绝佳的办法。不过可惜的是,你会失去对数据的控制权,但除了该法,我们别无选择。
2.不要使用内联方法计算数据
为了在控制器中直接过滤列表,不要使用可获得过滤链接的方法。“NG-重复”会评估每个表达式。在我们的案例中,“filteredItems()”返回过滤链接。如果评估过程很慢,它将迅速降低整个应用的速度。
l <li ng-repeat =“filterItems()中的项目”> //这并不是一个好方法,因为要频繁地评估。
l <li ng-repeat =“item in items”> //这是要采用的方法
3.使用两个列表(一个用来进行视图显示,一个作为数据源)
将要显示的列表与总的数据列表分开,是非常有用的模型。你可以对一些过滤进行预处理,并将存于缓存中的链接应用到视图上。下面案例展示了基本实现过程.filteredLists变量保存着缓存中的链接,applyFilter方法来处理映射。
/ *控制器* /
//基本清单
var items = [{name:“John”,active:true},{name:“Adam”},{name:“Chris”},{name:“Heather”}];
// Init displayedList
$ scope.displayedItems = items;
//筛选缓存
var filteredLists ['active'] = $ filter('filter)(items,{“active”:true});
//应用过滤器
$ scope.applyFilter = function(type){
if(filteredLists.hasOwnProperty(type){//检查过滤器是否被缓存
$ scope.displayedItems = filteredLists [type];
} else {
/ *非缓存过滤* /
}
}
//重置过滤器
$ scope.resetFilter = function(){
$ scope.displayedItems = items;
}
/ *查看* /
<button ng-click =“applyFilter('active')”>选择活动</ button>
<ul> <li ng-repeat =“displayedItems中的项目”> {{item.name}} <li> </ ul>
4.在其他模板中使用NG-如果来代替NG秀
如果你用指令,模板来渲染额外的信息,例如通过点击来显示列表项的详细信息,一定要使用ng-if(AngularJSv。1.1.5以后).ng-如果可阻止渲染(与NG-显示相比)。所以其它DOM和数据绑定可根据需要进行评估。
<li ng-repeat =“项目中的项目”>
<p> {{item.title}} </ p>
<button ng-click =“item.showDetails =!item.showDetails”>显示详细信息</ buttons>
<div ng-if =“item.showDetails”>
{{item.details}}
</ DIV>
</ LI>
5.不要使用NG-的mouseenter,NG-鼠标离开等指令
使用内部指令,像NG-的mouseenter,AngularJS会使你的页面闪烁。浏览器的帧速率通常低于每秒30帧。使用jQuery的创建动画,鼠标悬浮效果可以解决该问题。确保将鼠标事件放入的jQuery的.live()函数中。
6.关于过滤的小提示:通过NG-显示隐藏多余的元素
对于长列表,使用过滤同样会减低工作效率,因为每个过滤都会创建一个原始列表的子链接。在很多情况下,数据没有变化,过滤结果也会保持不变。所以对数据列表进行预过滤,并根据情况将它应用到视图中,会大大节约处理时间。
在ng-repeat指令中使用过滤器,每个过滤器会返回一个原始链接的子集.AngularJS从DOM中移除多余元素(通过调用$ destroy),同时也会从$ scope中移除他们。当过滤器的输入发生改变时,子集也会随着变化,元素必须进行重新链接,或着再调用$摧毁。
大部分情况下,这样做很好,但一旦用户经常过滤,或者列表非常巨大,不断的链接与
销毁将影响性能。为了加快过滤的速度,你可以使用NG秀和NG-隐藏指令。在控制器中,进行过滤,并为每项添加一个属性。依靠该属性来触发NG-节目。结果是,只为这些元素增加NG隐藏类,来代替将它们移除子列表,$范围和DOM。
触发NG-显示的方法之一是使用表达式语法.ng出现的值由表达式语法来确定可以看下面的例子:
<input ng-model =“query”> </ input>
<li ng-repeat =“项目中的项目”ng-show =“([item.name] | filter:query).length”> {{item.name}} </ li>
<span style =“font-size:14px; line-height:24px; font-family:; white-space:normal;”> </ span>
7.关于过滤的小提示:防抖动输入
解决第6点提出的持续过滤问题的另一个方法是防抖动用户输入。例如,如果用户输入一个搜索关键词,只当用户停止输入后,过滤器才会被激活。使用该防抖动服务的一个很好的解决方案请见:http://jsfiddle/Warspawn/6K7Kd/。将它应用到你的视图及控制器中,如下所示:
/ *控制器* /
//观察queryInput并将过滤消除350毫秒。
$ scope。$ watch('queryInput',function(newValue,oldValue){
if(newValue === oldValue){return; }
$ debounce(applyQuery,350);
});
var applyQuery = function(){
$ scope.filter.query = $ scope.query;
};
/ *查看* /
<input ng-model =“queryInput”/>
<li ng-repeat =项目中的项目| filter:filter.query> {{item.title}} </ li>
7总结
角上手比较难,初学者(特别是习惯了使用的JQuery的人)可能不太适应其语法以及思想。随着对纳克探索的一步步深入,也确实感觉到了这一点,尤其是框架内部的某些执行机制。
7.1页面效果
ng-show ng-hide无动画效果问题
7.2委派事件(代理事件)
7.2.1 NG循环及事件绑定
<UL>
<li ng-repeat =“数组中的a”>
<input ng-modle =“am”/>
</ LI>
</ UL>
伍会根据阵列的长度复制出Ñ个<LI>标签。而复制出的<LI>节点中还有<输入>节点并且使用了NG-MODLE指令,所以纳克会对所有的<输入>绑定监听器(事件)。如果阵列很大,就会绑定太多的事件,性能出现问题。
7.2.2 jQuery委派事件
从jQuery1.7开始,提供了。对()附加事件处理程序。
.on(events [,selector] [,data],handler(eventObject))
参数Selector为一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素。如果选择器是null或者忽略了该选择器,那么被选中的元素总是能触发事件。
如果省略selector或者是null,那么事件处理程序被称为直接事件或者直接绑定事件。每次选中的元素触发事件时,就会执行处理程序,不管它直接绑定在元素上,还是从后代(内部)元素冒泡到该元素的。
当提供选择参数时,事件处理程序是指为委派事件(代理事件)。事件不会在直接绑定的元素上触发,但当选择参数选择器匹配到后代(内部元素)的时候,事件处理函数才会被触发.jQuery会从事件目标开始向上层元素(例如,由最内层元素到最外层元素)开始吸泡,并且在传播路径上所有绑定了相同事件的元素若满足匹配的选择器,那么这些元素上的事件也会被触发。
委托事件有两个优势:他们能在后代元素添加到文档后,可以处理这些事件;代理事件的另一个好处就是,当需要监视很多元素的时候,代理事件的开销更小。
例如,在一个表格的tbody中含有1,000行,下面这个例子会为这1,000元素绑定事
$(“#dataTable tbody tr”)。on(“click”,function(event){alert($(this).text());});
委派事件的方法只有一个元素的事件处理程序,tbody,并且事件只会向上冒泡一层(从被点击的tr到tbody):
$(“#dataTable tbody”)。on(“click”,“tr”,function(event){alert($(this).text());});
许多委派的事件处理程序绑定到文件树的顶层附近,可以降低性能。每次发生事件时,jQuery的需要比较从事事目标(目标元素)开始到文档顶部的路径中每一个元素上所有该类型的事件。为了获得更好的性能,在绑定代理事件时,绑定的元素最好尽可能的靠近目标元素。避免在大型文档中,过多的在文档或document.body的上添加代理事件。
本文标签: AngularJS
版权声明:本文标题:转载的关于AngularJs的总结 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1726494419a1072905.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论