要理解angular双向数据绑定,首先要理解js的事件轮询---event-loop;html
JavaScript 运行机制详解:再谈Event Loop 这篇文章介绍的不错;
ajax
理解了以上内容以后就能够看下面这个文章;
api
下图与示例描述了Angular如何与浏览器事件轮循进行交互。网络
浏览器的事件轮循等待事件到来,事件能够是用户交互,定时器事件,或是网络事件(如 ajax 返回)app
事件发生,其回调被执行,回调的执行就使得应用程序的执行上下文进入到了 JavaScript 的上下文。而后在 JavaScript的上下文中执行,并修改相关的DOM结构异步
一旦回调执行完毕,浏览器就离开 JavaScript的上下文回到浏览器上下文并基于DOM结构的改变从新渲染视图async
讲了那么多些,那么Angular是怎么在这里横插一杠呢?看图,Angular是插进了 JavaScript的上下文中,经过提供Angular本身的事件处理轮循来改变正常的JavaScript工做流。它实际上是把JavaScript上下文很成了两块:一个是传统的JavaScript执行上下文(图中浅蓝色区域),一个是Angular的执行上下文(图中淡黄色区域)。 只有在Angular上下文执行的操做才会受益于Angular的数据绑定,异常处理,属性检测,等等。固然,若是不在Angular的上下文中,你也可使用 $apply()
来进入Angular的执行上下文。 须要注意的是,$apply()
在Angular自己的不少地方(如控制器,服务等)都已经被隐式地调用了来处理事件轮循。 显示地使用 $apply()
只有在你从 JavaScript上下文或是从第三方类库的回调中想要进入Angular时才须要。让咱们来看看具体的流程:ide
进入Angular执行上下文的方法,调用 scope.
$apply
(stimulusFn)
。上面 $apply()
中的参数 stimulusFn
是你想要让它进入Angular上下文的代码函数
进入 $apply()
以后,Angular执行 stimulusFn()
,而这个函数一般会改变应用程序的状态(多是数据,或是方法调用等)
以后,Angular进入 $digest
轮循。这个轮循是由两个较小的轮循构成,一个是处理 $evalAsync
队列(异步计算的队列),另外一个是处理 $watch
列表。 $digest
轮循不断迭代变动(在 $eval
和 $watch
之间变动)直到数据模型稳定,这个状态其实就是evalAsync
队列为空且$watch
列表再也不监测到变化为止。(译注:其实这里就是全部外来的异步操做堆起来成为一个队列,由$eval
一个个计算,而后 $watch
看一下这个异步操做对应的数据模型是否还有改变,有改变,就继续 $eval
这个异步操做,若是没改变,那就拿异步操做队列里的下个异步操做重复上述步骤,直到异步操做队列为空以及 $watch
再也不监测到任何数据模型变化为止)
$evalAsync
队列是用来安排那些待进入Angular$digest
的异步操做,这些操做每每是在浏览器的视图渲染以前,且经常是经过setTimeout(0)
触发。可是用 setTimeout(0)
这个方法就不得不承受缓慢迟钝的响应以及可能引发的闪屏(由于浏览器在每次事件发生后都会渲染一次)(译注:这里我的以为不要理解的太复杂,按照上面第三点理解就够用了,这边我的翻译的也不是太好,后期配以例子完善)
$watch
列表则是存放了一组通过 $eval
迭代以后可能会改变的Angular的表达式集合。若是数据模型变化被监测到,那么 $watch
函数被调用进而用新值更新DOM。
一旦Angular的 $digest
轮循完成,那么应用程序的执行就会离开Angular及 JavaScript的上下文。而后浏览器从新渲染DOM来反映发生的变化
接下来是传统的 Hello world
示例(就是本节的第一个例子)的流程剖析,这样你应该就能明白整个例子是如何在用户输入时产生双向绑定的。
编译阶段:
在{{greeting}}
插值(也就是表达式)这里设置了一个 $watch
来监测 username
的变化
执行阶段:
在 <input>
输入框中按下 'X
' 键引发浏览器发出一个 keydown
事件
input
指令捕捉到输入值的改变调用 $apply
("username = 'X';")
进入Angular的执行环境来更新应用的数据模型
Angular将 username = 'X';
做用在数据模型之上,这样 scope.username
就被赋值为 'X'
了
$digest
轮循开始
$watch
列表中监测到 username
有一个变化,而后通知 {{greeting}}
插值表达式,进而更新DOM
执行离开Angular的上下文,进而 keydown
事件结束,而后执行也就退出了 JavaScript的上下文;这样 $digest
完成
浏览器用更新了的值从新渲染视图