启动期
以下描述解释了一个简单的 Hello World! 应用的启动过程:
-
浏览器加载 HTML 并解析成 DOM
-
浏览器加载
angular.js
脚本 -
Angular 等待
DOMContentLoaded
事件 -
Angular 寻找
ng-app(指令)
,这个指令界定了应用程序的作用范围(边界) -
ng-app
中指定的Module
(如果有的话)被用来配置$injector(注入器)
-
$injector
用于创建$compile(编译器)
服务以及$rootScope(根作用域)
-
$compile
服务用于编译 DOM 并将其连接至$rootScope
-
ng-init(指令)
将 "World" 这个字符串赋值给当前作用域下的name
属性 -
{{ name }}(插入型表达式)
将name
属性插入 DOM,生成 "Hello World!"
源代码
<!DOCTYPE html>
<html ng-app>
<head>
<script scr="angular.js"></script>
</head>
<body>
<!-- 这里只是范例,实际上通常都是在 controller 里进行赋值而不是在这里 -->
<p ng-init="name='World'">Hello {{ name }}!</p>
</body>
</html>
运行期
以下内容描述了 Angular 如何与浏览器的事件回圈进行交互:
-
浏览器的
event-loop(事件回圈)
等待一个事件的到达;一个事件可能是一次用户交互,也可能是一个计时器,或者是网络事件(比如来自服务器的响应) -
事件的回调函数执行,这将进入 JavaScript 的语境(也就是 context,或称“上下文环境”)。回调函数可以修改 DOM 的结构
-
一旦回调函数执行完毕,浏览器离开 JavaScript 语境,并根据 DOM 的变化重新渲染视图
上图右边显示出 Angular 通过提供自己的事件处理回圈机制来改变 JavaScript 的执行流程,这种机制将 JavaScript 语境分成两部分:传统的执行语境和 Angular 的执行语境。只有应用于 Angular 执行语境的操作才能受益于 Angular 的数据绑定(data-binding),异常处理(Exception handling),属性监视(property watching),等等……也可以通过使用 $apply()
来进入 Angular 的执行语境。
要注意的是在绝大多数地方(控制器,服务),$apply()
已经通过指令(指令用来处理事件)被调用了(简言之,在 Angular 的执行语境下,没有必要显式调用它)。只有在需要执行自定义的事件回调函数,或是在处理第三方库的回调之时才需要显式调用 $apply()
。
-
通过调用
scope.$apply (stimulusFn)
进入 Angular 的执行语境。stimulusFn
既是你想要在 Angular 执行语境下做的一切事情。 -
Angular 执行
stimulusFn()
,通常这会改变应用程序的状态 -
Angular 进入
$digest loop
,这个回圈本身由两个更小的回圈组成,一个用来处理$evalAsync queue
,另一个用来处理$watch list
。$digest loop
不断地迭代直到模型稳固下来,技术层面上讲就是$evalAsync queue
为空并且$watch list
没有侦测到任何变化的时刻 -
$evalAsync queue
用来安排一些特定的(需要异步执行的)工作,这里特指那些需要出现在当前堆栈帧(current stack frame)以外(执行位置),并在浏览器视图渲染之前(执行时间)的工作。这种机制常常(非 Angular)是用setTimeout(0)
来实现的,但是setTimeout(0)
要么会变得缓慢,要么会引起视图闪烁(因为浏览器在每一个事件结束之后渲染视图) -
$watch list
保存了一组自上次迭代后可能发生了改变的表达式。在侦测到某个变动之时$watch
函数即被调用,并且通常会用新值来更新 DOM -
一旦
$digest loop
完成,并离开 Angular 和 JavaScript 的执行语境之后,浏览器就紧跟着去重绘 DOM 以响应任何变化
以下是另一个解释,描述了如何实现数据绑定效果(用户在文本框输入文字):
-
在编译期阶段:
-
ng-model
和input
指令为<input>
设置了一个keydown
事件监听器 -
{{ name }}
插值表达式产生了一个$watch
用以响应name
的变化
-
-
在运行期阶段:
- 按下 'x' 键会使浏览器触发设置在
<input>
上的keydown
事件 -
input
指令捕捉了这一变化然后调用了$apply("name='x';")
来更新应用程序模型(在 Angular 执行语境内) - Angular 将
name = 'x';
应用给模型 -
$digest loop
开始 -
$watch list
侦测到了name
属性的变化并且通知{{ name }}
插值表达式(令其重新解析),最终这将转变成 DOM 的更新 - Angular 退出自身执行语境,继而退出
keydown
事件和相关的 JavaScript 执行语境
- 按下 'x' 键会使浏览器触发设置在
源代码
<!DOCTYPE html>
<html ng-app>
<head>
<script src="angualr.js"></script>
</head>
<body>
<input ng-model="name">
<p>Hello {{ name }}!</p>
</body>
</html>