1.AngularJS的工作流程:
(1)浏览器载入HTML,然后把它解析成DOM
(2)浏览器载入angularjs脚本
(3)AngularJS等到DOMContentLoaded事件触发
(4)AngularJS寻找ng-app指令,这个指令指示了应用的边界
(5)使用ng-app中指定的模块来配置注入器($injector)
(6)注入器($injector)是用来创建“编译服务(compile service)”和“根作用域($rootScope)”的。
(7)编译服务是用来编译DOM并把它链接到根作用域的
(8)ng-init指令将model绑定
(9)通过{{ }}替换。
手动初始化AngularJS 等同于使用ng-app
<!doctype html>
<html xmls:ng="http://angularjs.org">
<body>
Hello {{'World'}}!
<script src="http://code.angularjs.org/angular.js"/>
<script>
angular.element(document).ready(function(){
angular.bootstrap(document);
})
</script>
</body>
</html>
angularJS编译器使用的是带指令的DOM,而不是字符串模版,它返回的是一个链接函数,这个函数和作用域模型结合就会生成一个动态视图,这个视图和模型的绑定过程是“透明的”。开发者不需要做任何关于更新视图的工作。并且应用没有用到innerHTML。
angularJS通过使用自己的事件处理循环,改变了传统js工作流。这使得js的执行被分成原是部分和拥有angularJS执行上下文的部分。只有在angularJS执行上下文中运行的操作,才能享受到angularJS提供的数据绑定,异常处理,资源管理等功能和服务。你可以使用$apply()来从别的js上下文进入到angularJS执行上下文。大部分情况下,$apply()都被采用来处理当前文件的响应指令执行过了,只有当你使用自定义的事件回调或第三方类库的回调,才需要自己执行$apply()。
1.通过调用$scope.$apply(fx)进入angularJS的执行上下文,这里的fx是你希望在angularJS执行上下文中执行的函数。
2.angularJS会执行fx,这个函数一般会改变应用的状态。
3.angularJS进入$digest循环。这个循环是由两个小循环组成的,这两个小循环用来处理$evalAsync列队和$watch列表。这个$digest循环直到模型“稳定”前会一直迭代。这个稳定具体指的是$evalAsync列表为空和$watch列表中检测不到任何改变了。
4.$evalAsync队列是用来管理那些“视图渲染钱需要在当前栈框架外执行的操作”。这通常使用setTimeout(0)来完成。用setTimeout(0)会有速度慢的问题,并且因为浏览器是根据事件队列按顺序渲染视图的,还会造成视图的抖动。
5.$watch列表是一个表达式的集合,这些表达式可能是自上次迭代后发生了改变的。如果有检测到了改变,那么$watch就会被调用,他通常会把新的值更新到DOM中。
6.一旦angularJS的$digest循环结束,整个执行就会离开angularJS和js上下文,这些都是在浏览器为数据改变而进行重渲染之后进行的。
作用域是用来检测模型的改变和为表达式提供执行上下文的。它是分层组织起来的,并且层级关系是紧跟着DOM的结构的。
视图背后的控制代码就是控制器。它的主要工作内容是构造模型,并把模型和回调方法一起发送到视图。视图可以看作是作用域在模版上的投影,作用域是一个中间地带,它把模型整理好传递给视图,把浏览器事件传递给控制器。控制器和模型的分离非常重要:控制器是由js写的。js是命令式的,命令式的语言适合用来编写应用的行为。控制器不应该包含任何关于渲染代码(DOM引用或片段)。视图模版用HTML写,HTML是声明式的,声明式的语言适合用来编写UI,视图不应该包含任何行为。控制器和视图没有直接的调用关系,所以可以使多个视图对应一个控制器。
模型就是用来和模板结合生成视图的数据。模型必须在作用域中时可以被引用,这样才能渲染生成视图。和其他框架不一样的是,angularJS对模型本身没有任何限制和要求。你不需要继承任何类也不需要实现任何指定的方法以供调用或改变模型。
视图是指用户所看见的。视图的生命周期由作为一个模版开始,它将和模型合并并最终渲染到浏览器的DOM中。浏览器将模版(HTML)解析成DOM,然后这个DOM会作为输入传递给模版引擎,也就是编译器。编译器查看其中的指令,找到指令后,会开始监视指令内容中响应的模型。这样,就使得视图能连续地更新,不需要模版和数据的重新合并。
每个angularJS应用都有一个唯一的注入器。注入器提供一个通过名字查找对象实例的方法。它将所有对象缓存在内部,所有如果重复调用同一名称的对象,每次调用都会得到同一个实例。如果调用的对象不存在,那么注入器就会让实力工厂创建一个新的实例。一个模块就是一种配置注入器实例工厂的方式,我们也成为提供者。
HTML的编译分为三个阶段
1.首先浏览器会用它的标准API将HTML解析成DOM。
2.DOM的编译是由$compile方法来执行的。这个方法会遍历DOM并找到匹配的指令。一旦找到一个它就会被加入一个指令列表,这个列表是用来记录所有和当前DOM相关的指令的。一旦所有的指令都被确定了,会按照优先级被排列,并且阿门的conpile方法会被调用。指令的$compile()函数能修改DOM结构,并且要负责生成一个link函数。$compile方法最后返回一个合并起来的链接函数,这个链接函数是每一个指令compile函数返回的链接函数的集合。
3.通过调用链接函数来将模版与作用域链接起来。会轮流调用每一个指令的链接函数,让每一个指令都能对DOM注册监听事件和建立对作用域的监听。这样最后就形成了作用域的DOM的动态绑定,任何一个作用域的改变都会在DOM上体现出来。
工厂函数是用来创建指令的。它只会被调用一次:就是当编译器第一次匹配到相应指令的时候。你可以在其中进行如何初始化的工作。调用它时使用的是$injector.invoke,所以它遵循所有注入器的规则。
指令定义对象给编译器提供了生成指令所需要的细节。则个对象的属性有:名称name、优先级priority、终端terminal、作用域scope(如果有多个在同一个DOM上的指令要求创建新作用域,那么只有一个新的会被创建。这一创建新作用域的规则不适用于模版的根节点,因为模版的根节点总是会得到一个新的作用域。@或@attr将本地作用域成语和DOM属性绑定。绑定结果总是一个字符串。=或=expression在本地作用域属性和父作用域属性间建立一个双向绑定。&和&attr提供了一种能在父作用域下执行表达式的方法)、控制器controller(控制器函数是在预编译阶段执行的,并且它是共享的,其他指令可以通过它的名字得到。会传递的参数有:$scope、$element、$attrs、$transclude)、请求require(请求将另一个控制器作为参数传入到当前链接函数,这个请求需要传递被请求指令的控制器的名字,如果没有找到,就会触发一个错误。请求的名字可以加上两个前缀:?(不要触发错误,这只是一个可选的请求)和^(若没找到,则在父元素的controller里面查找))、限制restrict(是用来限制指令的生命格式,若没有这一项,那就只允许使用属性形式的指令(E:元素名称 A:属性 C:类名 M:注释))、模版、替换replace、编译模版transclude、编译compile和链接link。
function compile(tElement,tAttrs,transclude){...}编译函数是用来处理需要修改模版DOM的情况,因为大部分指令都不需要修改模版,所以这个函数也不常用。需要用到的例子有ngTrepeat和ngView。编译函数接受以下参数:tElement-template element 指令所在的元素。 tAttrs-template attributes 这个元素上所有指令声明的属性,这些属性都是在编译函数里共享。 transclude 一个嵌入的链接函数 function(scope,cloneLinkingFn)。编译函数可以返回一个对象或函数。返回函数:等效于咋编译函数不存在时,使用配置对象的link属性注册的链接函数。返回对象:返回一个通过pre或post属性注册了函数的对象,使你能更具体的链接函数的执行点。
function link(scope,iElement,iAttrs,controller){...}链接函数负责注册DOM事件和更新DOM。它是在模版被克隆之后执行的,他也是大部分指令逻辑代码编写的地方。scope:指令需要监听的作用域。iElement-instance element:指令所在的元素。只有在postLink函数中对元素的子元素进行操作才是安全的。iAttr-instance attributes-实例属性:一个标准化、所有声明在当前元素上的属性列表,这些属性在所有链接函数间是共享的。controller-控制器实例:如果至少有一个指令定义了控制器,那么这个控制器就会被传递。控制器也是指令间共享,指令可以用它来通信。
Pre-linking function在子元素被链接前执行。
Post-linking function 所有元素都被链接后执行。
属性:作为参数传递给链接函数和编译函数。