ng中的$apply()


絮叨

这篇就来总结一下遇到 $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

  1. 第一轮:找到那个按钮,安上一个click监听器,设置一个定时器,结束本轮,然后浏览器根据需要看是否更新页面,然后开始等待接受用户的单击输入
  2. 第二轮:如果浏览器检测到一个单击事件在id为 clickMe 上的按钮发生了,那么Js引擎开启第二轮,这时候执行 buttonClicked 函数,当这个函数返回时,这一轮也就结束了
  3. 第三轮:从第一轮开始算起,2000毫秒之后,浏览器会开启新一轮执行,执行 timerComplete 方法

注意:事实上,上面第二轮或者第三轮的先后可能是反过来的,这样看在每一轮执行的间隔中发生了什么。

如何更新绑定的数据

一般情况下,有两种策略:

  • 通过特殊对象,这些对象里面存储的数据是通过方法存取的,get/set那一套,采用这种的有:EmberJS, KnockoutJS
  • 采用普通对象,就不用通过方法来设置对象中的字段、数据等,要做的就是等Js每轮执行最后检查是否有数据改变

AngularJS采用的就是上面所说的后一种策略,表面上看起来不高效,不能马上更新,但其实有一些策略可以提高这个性能的。

等到每轮执行的最后来检测数据是否有变更,在ng中是用 $scope.$digest() 方法,但其实我们从来很少直接调用这个,我们往往是通过一个切入点:$scope.$apply() 来让Js进入ng的digest loop,这样来更新所有的绑定还有监听器等。

尽量用$apply(fn)

带参数的这个方法不仅仅可以及时更新数据,而且它其实内置了 try...catch 来处理异常。

好了,今天就到这了,GN

左银右煌 /
Published under (CC) BY-NC-SA in categories programming  tagged with AngularJS  JavaScript