当浏览器触发DOMContentLoaded
事件时,Angular就开始工作。它首先寻找ng-app
指令。如果浏览器在DOM中找到ng-app
指令,它会为我们自动启动应用。如果没有找到这个指令,Angular期望我们自己手动启动应用。
要手动启动一个AngularJS应用,可以使用Angular的bootstrap()
方法。在一些罕见的情况下手动启用应用程序是有意义的。例如,想要在某个其他库的代码运行之后,或者在运行时动态创建元素时,启动AngularJS应用。要想手动启动应用,可以像下面这样启动它:
var newElement = document.createElement("div");
angular.bootstrap(newElement, ['myApp']);
如果在DOM中没有找到ng-app
指令,而且也没有手动启动应用,则AngularJS不会运行。忘记在页面中引入ng-app
指令肯定会引发一些严重的问题。
bootstrap()
方法只允许我们启动angular应用一次。
如果在ng-app
属性中没有指定应用程序,则Angular会加载一个不带特定模块的应用。如果指定了,Angular就会加载与这个指令关联的模块。
一旦应用程序加载完成,$injector
就会在应用程序的$rootScope
旁边创建$compile
服务。
$rootScope
创建后,$compile
服务就会接管它。它会将$rootScope
与现有的DOM连接起来,然后从将ng-app
指令设置为祖先的地方开始编译DOM。
视图的工作原理
当浏览器在标准的Web流程中获取HTML时,它会收到HTML代码并将它解析为一个DOM树。这个DOM树中的每个元素被称作DOM元素,这些DOM元素会构建一堆节点。然后浏览器负责绘制出这个DOM树的结构。
浏览器在提取脚本时(从<script>
标签中),会暂停DOM解析并等待脚本取回(你可以修改这一行为,但是这个行为是默认的)。
当angular.js
被取回时,浏览器会执行它,同时设置一个事件监听器来监听浏览器的DOMContentLoaded
事件。DOMContentLoaded
事件会在HTML文档加载完成并开始解析时触发。
检测到这个事件时,Angular会查找ng-app
指令,然后创建运行需要的一系列必要的组件(即$injector
、$compile
服务以及$rootScope
),然后开始解析DOM树。
编译阶段
$compile
服务会遍历DOM树并搜集它找到的所有指令,然后将所有这些指令的链接函数合并为一个单一的链接函数。
然后这个链接函数会将编译好的模板链接到$rootScope
中(也就是附属于ng-app
所在的DOM元素的作用域)。
它会通过检查DOM属性、注释、类以及DOM元素名称的方式查找指令。
$compile
服务通过遍历DOM树的方式查找有声明指令的DOM元素。当碰到带有一个或多个指令的DOM元素时,它会排序这些指令(基于指令的priority
优先级),然后使用$injector
服务查找和收集指令的compile
函数并执行它。
指令中的compile
函数会在适当的时候处理所有DOM转换或者内联模板,如同创建模板的副本。
// 返回一个链接函数
var linkFunction = $compile(appElement);
// 调用链接函数,将$rootScope附加给DOM元素
linkFunction($rootScope);
每个节点的编译方法运行之后,$compile
服务就会调用链接函数。这个链接函数为绑定了封闭作用域的指令设置监控。这一行为会创建实时视图。
最后,在$compile
服务完成后,AngularJS运行时就准备好了。
运行时
在标准的浏览器流程中,事件循环会等待事件执行(比如鼠标移动、点击、按键等)。当这些事件发生时,它们会被放到浏览器的事件队列中。如果有函数处理程序对事件作出响应,浏览器就会将event
对象作为参数来调用这些事件处理程序。
ele.addEventListener('click', function(event) {});
Angular中对事件循环做了一点增强,并且Angular还提供了自己的事件循环。指令自身会注册事件监听器,因此当事件被触发时,指令函数就会运行在AngularJS的$digest
循环中。
Angular的事件循环被称作$digest
循环。这个$digest
循环由两个小型的循环组成,分别是evalAsync
循环和$watch
列表。
当事件被触发时,事件处理程序就会在指令的上下文中进行调用,也就是AngularJS的上下文中。从功能上讲,AngularJS会在包含作用域的$apply()
方法内调用指令。Angular是在$rootScope
上启动$digest
循环时开始整个过程的,并且还会传播到所有子作用域中。
Angular进入$digest
循环时,会等待$evalAsync
队列清空,然后再将回调执行上下文交还给浏览器。这个$evalAsync
用于在浏览器进行渲染之前,调度需要运行在当前桢栈之外的所有任务。
此外,$digest
循环还会等待$watch
表达式列表,它是一个可能在上一次迭代过程中被改变的潜在的表达式数组。如果检测到变化,就调用$watch
函数,然后再次查看$watch
列表以确保没有东西被改变。
注意,对于$watch
列表中检测到的任何变化,AngularJS都会再次查看这个列表以确保没有东西被改变。
一旦$digest
循环稳定下来,并且检测到没有潜在的变化了,执行过程就会离开Angular上下文并且通常会回到浏览器中,DOM将会被渲染到这里。
整个流程在每个浏览器事件之间都会发生,这也是Angular如此强大的原因。它还可以将来自浏览器的事件注入到AngularJS流程中。