絮叨
这篇就来总结一下遇到 $apply()
的情况吧。
大致目前有这样几种:
- 在PouchDB中各种存取数据库的方法中,数据改变后需要通过
$rootScope.$apply(fn)
更新 - 其他异步操作或是别的库的操作对数据进行了改变,那也需要通过
scope.$apply()
来更新 - 所有跑出了ng的digest循环进入到原生js的API操作对数据的改变也需要
正题
想要真正了解什么时候是用 $apply()
,就需要理解为什么我们需要用它,下面是看这篇文章时的一些笔记与心得。
AngularJS and scope.$apply
原因:Js是按顺序执行的
原文标题是:Js is Turn Based,很简洁,我刚开始还把它翻译成转回Js为基础,尼玛,把turn当动词用了,后来想想,语法错了,2B了,turn这里是名词。
我们都知道,我们写的所有的Js代码并不是一次就全部执行完的,它们是按顺序执行的,而且是单线程的。
当有一个需要消耗一定时间(ms级)的任务需要执行时,比如一次ajax请求,等待一个单击事件,或者是设置一个timeout,我们总是把这样的任务丢到一个回调中,然后结束这一轮。之后,如果ajax请求完成,单击事件检测到,或者是定时器超时,这时候,新一轮Js执行开始,回调也就完成了它的使命。注意,这之间的时间间隔,是可以有新的Js代码被执行的,至于执行多少轮,那要看回调返回的时间了,回调返回总是强行插入的。看下面一个例子:
var button = document.getElementById('clickMe');
function buttonClicked() {
console.log('the button was clicked');
}
button.addEventListener('click', buttonClicked);
function timerComplete() {
console.log('timer complete');
}
setTimeout(timerComplete, 2000);
上面的代码其实有三轮执行,并非 completed in one turn :
- 第一轮:找到那个按钮,安上一个click监听器,设置一个定时器,结束本轮,然后浏览器根据需要看是否更新页面,然后开始等待接受用户的单击输入
- 第二轮:如果浏览器检测到一个单击事件在id为
clickMe
上的按钮发生了,那么Js引擎开启第二轮,这时候执行buttonClicked
函数,当这个函数返回时,这一轮也就结束了 - 第三轮:从第一轮开始算起,2000毫秒之后,浏览器会开启新一轮执行,执行
timerComplete
方法
注意:事实上,上面第二轮或者第三轮的先后可能是反过来的,这样看在每一轮执行的间隔中发生了什么。
如何更新绑定的数据
一般情况下,有两种策略:
- 通过特殊对象,这些对象里面存储的数据是通过方法存取的,get/set那一套,采用这种的有:EmberJS, KnockoutJS
- 采用普通对象,就不用通过方法来设置对象中的字段、数据等,要做的就是等Js每轮执行最后检查是否有数据改变
AngularJS采用的就是上面所说的后一种策略,表面上看起来不高效,不能马上更新,但其实有一些策略可以提高这个性能的。
等到每轮执行的最后来检测数据是否有变更,在ng中是用 $scope.$digest()
方法,但其实我们从来很少直接调用这个,我们往往是通过一个切入点:$scope.$apply()
来让Js进入ng的digest loop,这样来更新所有的绑定还有监听器等。
尽量用$apply(fn)
带参数的这个方法不仅仅可以及时更新数据,而且它其实内置了 try...catch
来处理异常。
好了,今天就到这了,GN