一、浏览器下载HTML/CSS/JavaScript等
当你转到一个页面地址后,浏览器先回下载这个HTML,同时,会开启一些辅助线程下载所关联的script标签和link标签里引用的文件。
二、浏览器构建DOM树
下载的同时浏览器会开始构建DOM树,内嵌或引入的脚本也会开始执行,也就是说浏览器会逐个加载DOM树中的每一个元素节点,可以把AngularJS当做一个类似jQuery的js库,通过<script>标签引入到HTML中,此时Angular被作为一个普通的DOM节点等待浏览器解析,当浏览器解析到这个节点时,发现它是一个js文件,那么浏览器会停止解析剩余的DOM节点,开始执行这个js(即angular.js)。
三、jQuery初始化
引用脚本中有一个是jQuery(如其不在AngularJS脚本之前引用,AngularJS就会启动其内置的jQLite),其启动代码会给自己挂接上document对象的DOMContentLoaded事件,通过调用jQuery.ready(callback)把一个回调函数注册到该事件中,后边DOM构建完毕时候,会触发该事件,并执行回调函数,会开始启动Angular,在触发之前,还有许多脚本要初始化,也就是DOM还未解析完成。
四、AngularJS初始化
需要初始化的脚本中有另外一些AngularJS模块及其子模块,或一些第三方模块,此时会按引用顺序开始它们的初始化过程。模块的初始化过程大致相同:
1、按名字创建模块,是一个对象,是其他Angular对象的注册表。
2、在这个模块中注册各种Angular对象,如Controller、Service、Directive等,形成一个有名字和回调函数组成的对照表,这些回调函数现在还不会执行。
3、模块中注册“config回调函数”和"run回调函数",分别在模块开始加载和加载结束时候执行,现在只是注册,不会执行。
五、jQuery启动
当页面及其直接饮用的js文件都下载执行完后,DOM构建完成,浏览器会触发document对象的DOMContentLoaded事件,在jQuery.ready中注册的回调函数也会调用,其中代码会开启动AngularJS。
六、AngularJS启动
此时,AngularJS正式登场,第一件事就是查找一个带有ng-app指令的节点,通常出现在body或html元素中,也可以出现在任意节点上,而且,一个页面可以有多个这样的节点;AngularJS找到第一个带有 ng-app节点,并调用angular.bootstrap(element,moduleName),element就是该节点,moduleName是该节点上指定的模块。
AngularJS自启动方式,只会启动第一个ng-app的module;对于多个ng-app启动的方式,必须采用手动angular.bootstrap来启动。
推荐,仅适用一个ng-app,然后,用Module和Controller来划分页面。
七、加载子模块
AngularJS首先会创建一个注入器(injector),并关联到所在节点上,前边模块注册的一大堆Angular对象,都需要它才能被其他代码调用;接着,对当前节点的模块和所依赖模块进行初始化,顺序执行所有的"config回调函数";
在config回调函数中能够使用的只有注册的常量(Constant)对象和Provider类,这里也是程序中唯一可以直接访问Provider类对服务进行配置的地方。
比如,路由服务的Provider就是在这里初始化,但是,只是负责记录一个URL到“模板/控制器”组的映射表,供后边使用。
八、启动子模块
模块加载完成后,会执行所有“run回调函数”,在这个阶段,各种Angular对象都可以使用了(如果想使用),包括各种Service、Factory等。
接下来,控制权会转到路由模块,使用$location服务解析当前页面的URL,然后,根据这个URL查找响应的“模板/控制器”对(七、中生成的),来渲染一个页面。
九、渲染页面
路由模块会先创建一个Scope对象,并加载模板,加载完毕后把它的内容传给$complie对象,其会先把它解析成一个静态DOM树,然后逐个扫描这棵DOM树种的指令,通过这些指令把Scope对象DOM树关联起来,包括渲染内容的函数和进行事件处理的函数。最后,用它替换一个特定指令所在的节点,即,ng-view/ui-view。
$compile服务通过遍历DOM树的方式查找有声明指令的DOM元素。当碰到带有一个或多个指令的DOM元素时,它会排序这些指令(基于指令的priority优先级),然后使用$injector服务查找和收集指令的compile函数并执行它。每个节点的编译方法运行之后,$compile服务就会调用链接函数。
十、数据绑定和摘要循环
此时,页面已经显示出来,但是数据还没有被渲染,AngularJS会自动使用Scope中的数据渲染一遍。
通过”脏检查机制”,使得用户修改了数据,也能被渲染出来。
它是一个称为“摘要循环”的过程,AngularJS会给每一个Scope成员变量求出一个 摘要值(能唯一标识一个变量),并保存在一个变量中,当调用Scope的$digest/$apply方法时候,会重新计算一遍摘要值,只要数据变化了,就会更新界面。
也就是说,Angular提供了自己的事件循环。指令自身会注册事件监听器,因此当事件被触发时,指令函数就会运行在AngularJS的$digest循环中。$digest循环会等待$watch表达式列表,当检测到模型变化后,就会调用$watch函数,然后再次查看$watch列表以确保没有
模型被改变。一旦摘要循环稳定下来,并且检测到没有潜在的变化了,执行过程就会离开Angular上下文并且会回到浏览器中,DOM将会被渲染到这里。
注意:$digest不用直接调用,$apply是对它的封装,其也一般很少调用,因为在一些AngularJS事件指令以及$timeout等服务中,会自动调用它来确保界面刷新。
自己挂接第三方组件的事件,就得记得手动调用一次$apply。
到此,一个典型的AngularJS程序就启动成功了。