Angular双向数据绑定原理探究。
文章源码引用较多,觉得难以理解可以直接跳到末尾总结处。
接触过Angular的人一定会对其“双向数据绑定”的特性印象深刻,而使用过的人更会对莫名其妙出现的双向数据绑定失效的“坑”所困扰。例如下面一段代码:
|
|
源码地址:http://jsbin.com/xogosim/edit?html,js,console,output
如果上面代码中的两个问题你都知道答案,那么你可以跳过下面的内容,如果并不完全清楚,那么我们接着往下说~
双向数据绑定,指的是视图和模型之间的映射关系。双向即 视图 ==> 模型 和 模型 ==> 视图 两个方向。
我们以Angular1.3为例,探究一下这个问题。
视图 ==> 模型
抛开Angular不说,如果我们要实现视图修改时触发模型的修改,很简单,事件(键盘事件、鼠标事件、UI事件)监听就能实现。而Angular会不会也是这么实现的?
最常用的场景便是表单元素的数据绑定,当元素的值发生变化时我们要通知模型层(比如校验、联动),例如用于实现这一功能的 ngModel
指令。
但是我们如果直接找到ngModel
的源码,并没有找到直接的事件绑定,依赖ngModelOptions
指令倒是有一段代码绑定了事件
|
|
可是平常没使用ngModelOptions
的时候也能同步元素的修改,难道是一开始就想错了?
回忆一下Angular定义指令的时候,不光有像ngModel
这样通过属性定义,也有直接定义成元素的,例如form
就是一个指令。而最常用最简单的就是把ngModel
用在input
元素上,不,应该是input
指令。
于是找到input指令的代码
|
|
发现只要nhgModel
指令存在的时候,它就会根据type属性执行一段函数。
我们找到inputType.text
这个函数之后,层层追寻…
终于找到了它在绑定事件的证据,而且还很智能,根据浏览器对事件的支持情况来进行绑定。
发现绑定的事件都执行了一个函数:$setViewValue
。继续查找,发现调用ngModelSet
函数来修改模型。
模型 ==> 视图
我们再次抛开Angular,回到原生实现,如果我们想要修改视图也比较简单,获取dom元素并修改对应的属性。
再找一个在Angular中将模型值同步到dom上的指令ngBind
。
|
|
发现其在scope.$watch
回调函数中来修改dom元素的文本内容。那我们可以大胆地推测,应该是在修改了对应的$scope
属性值之后,触发了scope.$watch
调用了ngBindWatchAction
回调函数才导致页面元素文本变化的。
|
|
从源码中可以看到,当我们在调用$watch
监控变量的时候,其实是创建了一个watcher
对象,并将其放入$scope.$$watchers
数组中。
那么谁会用到这个数组,并且其中的回调函数呢?
这个代码有点难找,直到找到一个叫做$digest
的函数定义。
|
|
简单概括一下这段代码,遍历$scope.$$watchers
,判断如果需要检测的表达式的值(可以理解为$scope的属性)发生了修改,那么执行对应回调函数(比如ngBindg中的ngBindWatchAction)。
修改$scope对应的属性,并调用$scope.$digest
。完成这两个条件即可同步模型数据到视图,修改dom元素。换句话说,这两个条件缺一不可。而调用$scope.digest
这一过程,我们一般叫做脏值检测。
有人可能会说我调用$scope.$apply
也可以啊~
理论上来说,用$scope.$digest
完成的手动试图同步都可以用$scope.$apply
,但是他们之间还是有区别。
|
|
区别就在于,$apply是对$rootScope
及子作用域做脏值检测,意味着性能消耗更大。支持回掉函数算是一个好处。
总结
视图 ==事件绑定==> 模型
模型 <==脏值检测== 模型
一部由众多技术专家推荐, 帮你成为具有全面能力和全局视野工程师的进阶利器—— 《了不起的JavaScript工程师》出版了! 点击下方链接即刻踏上进阶之路!
- 淘宝:https://detail.tmall.com/item.htm?id=600756390664
- 京东:https://item.jd.com/12562349.html?dist=jd
- 当当:http://product.dangdang.com/27922044.html