angular原理及模块简介
Posted E-HERO
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了angular原理及模块简介相关的知识,希望对你有一定的参考价值。
本人前端小白,奈何在公司在做一个PC端的程序,用angular写,不得不自学了一下angular框架。虽然在工作过程中勉强勉强够了,但是觉得既然用了就稍微了解得全面一点,所以花了几个晚上看了一下angular的developer guide,大概知道了一点点angular这个东东到底是干啥,后来又花了几个晚上做点小记录,防止以后忘了。
Angular简介(大神可略过)
Angular是一个强大的前端框架,其强大之处主要是可以把静态页面与动态数据绑定起来。平时我们看到的网页界面上面的数据都是固定,但如果我们要变化这些数据,例如我在一个文本框输入,要实时改动一个文本,肿么破。这时候有两种方法(我只想到两种,求大神告知更多):
1.改变一下,就请求一下后端,例如php,然后后端重新返回一个更新好的页面,当然这种方法很傻,改变一点小数据就请求后端,的确太傻(由于前端小白,我之前就用这种方式做了一个小网站,后来接触到angular才发现自己太傻);
2.通过js改写DOM(for 小白:这里的DOM可以理解为html,但官方称呼叫document object model,小白我以前总是不知道这是啥),js最初的document类就可以干这事儿,后来出现了一个jquery(js的一个强大的库),也可以方便的改写DOM,对于这些前端就可以知道的数据就不用请求后端了。
jquery也只是一个库,提供了一些简单改变DOM的方法,对于简单的小工程来说也够了。但是对于比较大的工程,考虑的不仅是功能的实现,还包括可维护可扩展,这就需要MVC模式了(for小白:至于啥是MVC,可以看我介绍spring框架那篇博客里面)。如果只用jquery,view的逻辑会和c,m的逻辑混在一起,不便于维护,例如你在文本框里数据了一个东西,你得用写代码去获取这个值,然后做处理,或者你的某个值改变了,你还得写代码去更新一下view,而angular就是提供这样一个解决方案的框架(后面还会有介绍感觉angular的强大)。
Angular里面的html文件就是view,叫模板(template),当你的数据变化需要改变模板的时候,不用再js代码里面去改变,你可以什么都不做,因为angular神奇的地方就是把模板与数据绑定(data binding),当数据改变的时候模板自动就变了,你的view变了(在文本框输入东西了)也会自动反应到你的数据上面,这就是双向绑定。在angular的理念里面,模板就是一副素描画,数据就是颜色,你想做完这幅画,只需要向模板填充你想要颜色就行了(也就是填充你的数据),例如下面这个例子,你的输入自动显示到界面上
http://www.runoob.com/try/try.php?filename=try_ng_intro
你只需要专注你的数据和模板就够了,他们之间怎么填充,angular把这些做好了,也就是剥离了view层对contorller,mdoel层的影响,下面就是angular官方给出的区别
一般处理数据:
angular:
引用自:https://docs.angularjs.org/guide/databinding
简单来说就是你用angular了数据和view自动双向绑定,不用你再代码中去更新,不用angular你还要自己写代码在view变了时候去更新数据,在数据变了的时候去更新view。
angular原理
angular是基于js的一个框架,首先需要了解一下js的工作原理。
JS原理
浏览器里面有一个事件队列(event queue),用户触发啥事儿,或者网络请求,延时操作(例如定时器之类),都是一个event,浏览器会轮训这些事件,然后调用这些回调(这里的回调简单来说可以理解为触发一个函数),然后就进入javascript的环境中执行(JavaScript context),在这里面可以改变数据,操作DOM(也就是html结构),然后再退出JavaScript环境,又进入浏览器环境,然后浏览器根据之前的改动重新绘制界面,这就是个一个流程。
下图为angular 官方解释(引自https://docs.angularjs.org/guide/scope )
angular原理
angular的运行就是在JavaScript context里面自己实现了一套环境,叫做angular环境(angular context),非angular那部分环境叫经典环境(classic context),
在angular context里面也有一个队列,这个队列里面是watch列表,列表里面装的就是那些被监听的变量,包括那些进行数据绑定的变量(也就是和view进行绑定的那些)。如果用户改变了一个绑定了数据的view,这时候会触发一个angular函数$apply(也就是把这个event放入了event queue,然后轮训到这个的时候就触发了),然后把这个改变的值更新进绑定的那个变量,再开始调用一个digest的函数,digest就是用来轮训这个watch列表,看这个列表中的指是否变动,如果有变动就变动改写相应的DOM(不用angular就要自己写这部分代码,如果你有100个变量,你就要写100个这种改动,而且如果以后有啥变动,还得自己去重构)。
关于angular原理机制的一些参考:
http://www.cnphp6.com/archives/64167
http://www.tuicool.com/articles/fAfiMv
这里还有两点注意,
1.angular会至少轮训两遍watch列表,为啥?因为第一次轮训可能在改写DOM的时候可能会触发其他watch列表里面的变量变化,这时候还会再轮训,直到连续两次轮训的变量不再变化。所以如果你有两个变量的变化是相互影响的,就是A变了触发B变,B变了触发A变,这样会引起死循环,angular好像是在轮训5次(或者是10次,具体我忘了),如果还发现值没有稳定,就会报错(我曾经就干过,界面突然卡死,整个浏览器都卡死了,好不容易打开控制台看,全部是angular轮训报的错,angular的轮训直接卡死了整个浏览器)。
2.另外还有一点,关于效率问题,有人提出来angular这样无差别轮训可能会影响性能,但是angular的创始人给了解释,人能在一个页面上最多就能看200个元素,在一个web页面上面不会有这么多的元素绑定数据,如果绑定这么多元素需要实时更新,那属于网页设计的问题(引自stackoverflow,具体网址找不到),所以并不用担心轮训的效率问题,如果真的有效率问题,说明网页本身可能存在问题(在豆瓣上看到一篇帖子,一个人用angular测了500个ngModel绑定的页面,很卡,所以对于不必要的绑定,最好不要绑)
angular组件
Controller
Controller是angular一个重要的组件,基本用angular一定会用到controller。Controller顾名思义,用来控制的,是MVC中的C,逻辑控制。在angular里面,controller是一个JavaScript的构造函数,这个函数有两个作用,初始化scope,还有就是增加方法(add behavior)。
引自:https://docs.angularjs.org/guide/controller
这里稍微简单解释一下scope(后面会说一下),scope是一个对象(object)可以理解为是连接view和controller的一个桥梁,scope的属性中有一些值,有一些方法(behavior),在html中可以直接访问到,如下图,ng-click里面的那些方法都是scope的一个属性,还有显示出来的那些值。在scope里面初始化之后,在view(也就是html中,angular官方叫做template)里面就就可以访问到这些值,也可以触发这些方法,这也就是angular数据双向绑定的具体使用(很简单吧,不用写一堆jquery了)。
引自:https://docs.angularjs.org/guide/controller
所以controller的作用就在于上面说的,初始化scope和为scope增加方法,同时angular官方也给出了一些不建议使用的方式(如下图),因为这样操作基本上都有更好的方式
引自:https://docs.angularjs.org/guide/controller
Tips:
1. angular在1.2版本后多了一个controlleras的语法,这个语法允许为controller起个别名(有木有感觉像sql里面的as),如下图
如果不用这个语法,需要在controller这个函数里面依赖注入(Dependency Injection,后面也会介绍到,如上面那个例子所示,在函数的参数里面写个$scope)。如果用这个语法就不用了,两者的不同就在于不用controller as这个语法,html通过访问scope的属性来访问数据,所以要把给html访问的数据写进scope的属性,如果用controller as,整个controller这个实例(例子中的demo)会作为scope的一个属性,例如html要访问一个data属性的值
Controller as 访问的是scope.demo.data
Controller 访问的是scope.data
2. controller继承,每个controller继承其实是scope的继承,可以简单理解为JavaScript的继承,具体可以看我另一篇博客(如果有时间发的话T^T),或者是angular官方文档上面说的。在这里就是如果子controller里面没有指定这个数据,就会用父类的,如果指定了就用子类的,但是要注意如果是改变model里面的值,有可能改父类的值(下面还会介绍这个概念)
Service
Service可以理解为MVC结构中的M层,来处理具体的业务逻辑,最理想的代码就是在view里面触发了controller中的函数,然后controller来调用model里面具体的处理,然后model返回给controller改scope的数据,反应在view上面。Service就是这个作用,在angular里面,service有两个特点
1. 懒加载(lazy loading):只有在需要用的时候(也就是在其他service,filter,directive或者controller里面依赖注入的时候才会生成这个service实例)
2. 单例模式(singleton):service在angular里面是单例(singleton),只在第一次被注入的时候创建实例,然后存在cache里面,等需要的时候(也就是另外的依赖注入的时候),从cache里面取出。所以service的生命周期只要创建之后,除非app退出,否则一直都有这个实例。不能销毁(我还没找到一种手动销毁的办法,事实上在网上查了一些需要销毁的例子,其实都可以用其他方法来做,不一定非要销毁这个service实例)
http://stackoverflow.com/questions/32781488/how-to-destroy-an-angular-factory-instance
PS:如果在使用过程中需要多例的样子,可以自己稍微改动一下,把service当做一个factory模式,返回各种需要的实例。可以参考下面的连接(两个例子其实一样,只是在service的写法上有点不同而已,另外提示链接里面的网页在embed标签栏里面可以看到样式)
http://jsfiddle.net/rbdmjLok/3/
http://jsfiddle.net/jeremylikness/rbdmjLok/
- 注册service
在官方给出的developerguide里面主要介绍了两种方式,factory模式和provider模式,但其实还有一种service模式,所以总共有三种:
1. factory:angular里面比较常用的一种方式,注册一个function,这个function在生成实例的时候会被调用到,这个函数返回一个service实例(要自己写return的),所以只要把自己需要的service写成一个obj,在这个obj里面定义要的方法和值,然后再最后return一下这个obj就可以了,如下图
2. service:service注册就更简单了,相当于对factory做了一层封装。只要在service里面写需要的方法和值就行了,不需要return,如下图
3. provider:provider是angular里面注册service最底层的方式,无论是service方式注册还是factory注册,其实底层都是用的provider这种方式。在provider里面有一个$get的属性,这个属性是一个函数,这个函数就是factory里面我们写的那个函数,用service那种写法在这里就是new 一个service的那个function赋给它(js里面function也可以看做一个对象的,所以等于直接new了一个对象给$get),angular就是通过在依赖注入(后面会介绍)的时候,调用这个函数获得一个实例。
如果一个service只有在实例化它之前才知道一个配置,例如读文件或者网络返回的一些动态配置,这种就不能在代码里面写死,需要传参数给service初始化(有点类似于Java,C++里面的带参数的构造函数),这时候就需要在provider里面配置。所以provider就是在service初始化前对service做一些配置的组件(所以叫provider嘛),在provider里面留一个接口(也就是留一个函数),等获取到需要的配置的时候调provider的这个接口,就可以设置service的参数。注意:这里的provider只是提供了这样一种方法,可以把它理解为一种工具,config才是调用provider的东东。至于为啥不直接用provider调用,而还要加个config,我一开始想不通,后来问了我boss,他认为这只是为了好理解,让人一看就知道是配置。后来我也想了一下,应该也是为了方便统一配置。如果直接调provider里面建,要么分开写,要么还要自己写一个function在里面配置,angular大概应该是为了提供一个统一并且方便理解的接口吧(个人理解)。具体例子如下(注意:在provider里面写的名字,在用的时候会在名字后面加一个provider,例如例子中写的User,但实际调用的那个provider是UserProvider)
在使用以上三种哪种方式,官方文档似乎没有给出啥太明显的建议,个人是这样认为的,service更倾向于是一种服务,factory倾向于一个工具,provider是需要对这个service做一些配置。下面的博客写得很好,里面也有介绍啥时候用哪种模式,附上一些参考。
https://my.oschina.net/tanweijie/blog/295067(上面几张图引用于此)
https://docs.angularjs.org/guide/services
http://stackoverflow.com/questions/15666048/angularjs-service-vs-provider-vs-factory?rq=1
scope
scope是连接controller和view的桥梁,angular官方是这样描述的:Scopeis the glue between application controller and the view(如上面的例子可以看出)
- scope对外api:
scope主要提供三个对外API(官方文档中developer guide里面只提及了两个)
1.watch:
监听model是否发生了变化,注意这里的watch提供三种api监听
(1)(scope.$watch(watchExpression, listener)) :只监听对应的值或者reference是否变化,如果变化就触发注册的回调函数(也就是那个listener)
(2)(scope.$watchCollection(watchExpression, listener)) :监听对应的值或者reference以及集合里是否发生变化(例如集合增加或者减少,但是不包括集合里面的值变化)
(3) (scope.$watch (watchExpression, listener, true)):监听对应的值或者reference以及集合里是否发生变化并且还包括里面的值是否发生变化,下图可以比较清晰的看出其中的区别
2.apply:
在angular context外发布model变化的消息(PS:如果在angular context外变化angular是不会更新界面的,例如用setTimeOut这种方式来更新model,因为setTimeOut只是把一个event放入了队列里面,不会马上执行,等到执行注册timeout的这个function的时候,如果是完全和angular无关的,也就是没有用到angular的一些内置命令,这是不会触发进入angular context的,所以这时候的运行完全就是在angular context外,所以即使更新model的数据,也不会在view上面显示出来,所以要在外面更新一般要自己调用一下apply,具体例子可以参考下面给的那个博客。一般在ng开头的命令中和angular自带的一些service里面都会自动调用apply,所以我们不需要去调用)
3.digest:
这个在angular官方文档中没有列出来,但其实也是可以直接调用的,官方应该是不推荐这样做。调用apply就会调用digest,digest会轮训那些watches(注册了监听的那些值的列表),如果发现值变化了会调用watch注册的那个function来进行一些处理,可以理解为apply->digest->watch
参考自:http://www.cnphp6.com/archives/64167
- scope种类
scope分为两种,一种是child scope,一种是isolatescope,前者是按照类似DOM结构的继承关系,后者是完全独立的(一般用在directive中,因为directive一般是脱离上下文,能够单独使用的,例如要做一个通用的列表,在用的时候只需要传个列表值进来就可以了,这种和上下文无关,所以一般是独立的,就类似于android里面的adapter一样)
- scope继承
对于child scope的继承,就和JavaScript的继承差不多,简单来说就是如果子scope中没有的属性,会去父scope去找,一层一层去找.
如果这时候想赋值,不会改到父scope中的属性,例如parentScope.a= 1,如果childScope中没有指定a那么childScope.a也是1,但是如果这时候赋值childScope.a = 2,这时候parentScope.a还是1,为啥,因为那个赋值语句对于JavaScript不是一个改变值的语句,是为childScope创建了一个a的属性值等于2,所以父parentScop不受影响。但是如果属性是model(也就是对象)就不一样了,以为访问model的时候传的reference(这里和C++是不一样的,Java和JavaScript里面都是把类作为reference传,C++是通过拷贝构造函数拷贝一份,除非修改拷贝构造函数,否则默认是传值),例如:parentScope.a.value = 1,如果childScope没有指定,那么childScope.a.value也是1,这时候赋值childScope.a.value = 2,那么这时候父parentScope.a.value也是2。为啥,因为childScope.a是访问parentScope的属性(如果childScope里面没有指定,注意这个前提),由于a是个model(对象),所以访问的是地址(reference),这时候a.value=2就是对这个地址的值进行了改写,所以parentScope也会被改变。当然如果先把childScope.a=newA,这样childScope指向的就不是parentScope的了,这时候再改a的值就不会影响到parentScope了。建议可以在自己在console里面试试(JavaScript中function其实就是类,我的另外一篇博客也介绍了关于JavaScript和angular的继承关系)
参考自:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
https://docs.angularjs.org/guide/scope
- 追踪scope
这是angular官方给出的怎么在view中调试scope,也就是看scope当前的一些值
其实还有另一个方法,也就是我们在项目中用到的一个方法,设一个全局变量,然后再每个controller里面都把scope赋值给这个全局变量,这样可以在console里面从这个全局变量里面看到想追踪的scope的值了。但是注意:如果这个全局变量只是为了调试,不要在代码中使用这个全局变量,也就是不要读取,因为这个存在只是作为调试使用的,是随时会去掉的一个东西,如果有代码逻辑依赖这个全局变量的值,在去掉之后会导致错误的。所以不要让代码依赖一个随时会去掉的变量。
- Scope事件分发
这个在项目中我们基本没用过,但angular提供了这个机制,emit和broadcast(做Android的同学应该对这个单词比较有感觉吧,但其实这类似于Android里面的事件传递机制event dispatch,例如touchEvent和clickEvent这类,但angular这个似乎不存在消费,因为项目没用这个,所以也没仔细考证,求大神告知)
emit:
释放事件,当前scope和父scope都可以收到这个事件,如果在对应的scope里面有注册这个scope的回调,就会调用这个回调函数。
Broadcast:
发布事件,当前scope和子scope都会收到这个事件,如果在对应的scope里面有注册这个scope的回调,就会调用这个回调函数。
具体例子可参考
https://docs.angularjs.org/guide/scopeScope Events Propagation部分
DependencyInjection(依赖注入)
依赖注入是在很多编程语言和框架中都会提及的一个东西。其实也很好理解,首先什么是依赖,A模块(例如类,方法),需要用到B模块中的东西,这时候就说A对B有依赖,例如A类里面有个add的方法,在B类里面需要用到这个这个add的方法,就是B对A有依赖。这时候就需要注入(其实注入也可以理解为初始化这种意思),在Java里面可能就需要new 一个A,在angular里面,就直接用函数参数这种形式来写,但是要先在其他地方定义这个依赖的类,就是要定义一个service(如上面提到的service那部分),然后再把这个service作为一个函数参数传进来。这样就相当于new了这样一个对象。具体可以参考上面service部分的例子或者angular 官方的例子
依赖注入写法在angular中有三种:
1.Inline ArrayAnnotation:
在中括号里面用单引号写上,并且在function的参数里面写上,而且要注意顺序一致
2.$inject PropertyAnnotation
用$inject来写,同时也要注意参数顺序一致
3.ImplicitAnnotation
只在function的参数里面写,最简单的一种写法,但是也是angular官方不推荐的。因为这种写法在代码混淆中会出问题,当然也有一个工具解决这个问题,这里就不提及了,详见angular官方文档https://docs.angularjs.org/guide/di
另外angular还提供一种严苛模式(Android里面其实也有一种严苛模式,但是和angular这个不同,Android的同学不要弄混了),不允许Implicit Annotation,一旦用了Implicit Annotation就会报错,这里也就不介绍了,详见angular官方文档同上。另外关于解决依赖的问题,其实有三种方式(如下图),但angular认为前两种方式不好,因为前两种要自己编码,比较麻烦,特别是在单元测试的时候,所以才用第三种(spring框架也是用的这种方式),angular里面主要就是靠$injector来创建和追踪依赖,所以减轻了开发者的负担。更多详情参考angular官方文档,这里就不提及了。
template
angular里面的template其实就是html,在angular中,可以用下面四种方式来控制模板(html)的显示(如图),都比较简单,看看例子就知道了,这里就不提及了。angular建议如果是简单的app,可以把所有html写在同一个html文件中,然后用directive这些来控制(一般就是index.html),如果比较复杂一点的app,可以把不同的view(也就是html)放在同一个page里面,但是把各自view定义在不同的html文件中,然后通过引用的方式在加载在这个page中(我们的项目就是通过这种方式来做的,其实也可以认为是一个index.html,但是里面的很多view都来自于其他html文件)
参考自:
https://docs.angularjs.org/guide/templates
后记:本来想仔细研究完angular官方文档再发,但后来发现已经写了六千多字了,放在一篇里面发不太好,刚好这里面介绍的都是angular一些基本的组件,算是基础篇吧。其次就是刚好赶上公司业务调整,现在还需要接手其他业务线的工作,后面可能也不太有时间写多少js了,所以先发出来,也算是对过去一段时间写angular的一个小总结,希望以后有空再写个这篇博客的续集吧。
因为前人,才能更高
1 .菜鸟教程:http://www.runoob.com/angularjs/angularjs-tutorial.html(初学者可以看看这个,能够快速掌握一些基础的使用)
2. angular官方文档developer guide:https://docs.angularjs.org/guide/concepts(可以从concepts这个模块往后看,本文大部分是基于angular developer guide,主要来自同一个网站,文章中已经指明,在此就不一一列出各模块的引用了)
3.angular工作原理:http://www.cnphp6.com/archives/64167
4.angular工作原理:http://www.tuicool.com/articles/fAfiMv
5.stackoverflow关于如何销毁service:http://stackoverflow.com/questions/32781488/how-to-destroy-an-angular-factory-instance(最后回答者建议不要销毁,其实可以用其他方式来实现业务逻辑,这里主要是想说明其实很多时候不用非要销毁service,可以用其他的方式来做)
6.多态service的写法:http://jsfiddle.net/rbdmjLok/3/ (例子为一个计数service,在两个地方分开计数的例子,一个service多处独立使用)
7.service三种构造方法的区别:https://my.oschina.net/tanweijie/blog/295067
8.service三种构造方法的区别:http://stackoverflow.com/questions/15666048/angularjs-service-vs-provider-vs-factory?rq=1
9.JavaScript以及angular之间的继承:https://github.com/angular/angular.js/wiki/Understanding-Scopes(这篇文章挺好的,值得一读)
以上是关于angular原理及模块简介的主要内容,如果未能解决你的问题,请参考以下文章