event loop 与 vue

结论

对于event loop 能够抽象成一段简单的代码表示javascript

for (macroTask of macroTaskQueue) {
    // 1. Handle current MACRO-TASK
    handleMacroTask();
      
    // 2. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

js事件机制

javascript是一个单线程语言,同一时间只能执行一个任务。
对于javascript的事件处理机制,咱们能够简单理解成“主线程+任务队列”模式。主要步骤以下html

(1)全部同步任务都在主线程上执行,造成一个执行栈。

(2)主线程以外,还存在一个 "任务队列"(task queue)。只要异步任务有了运行结果,就在 "任务队列" 之中放置一个事件。
(3)一旦 "执行栈" 中的全部同步任务执行完毕,系统就会读取 "任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。java

(4)主线程不断重复上面的第三步。web

任务队列

任务队列分为task queue和microtask queue。执行栈任务清空后会先从Microtasks中取任务,Microtasks中执行完以后才会执行task中的任务。segmentfault

所以一个event loop主要流程以下:api

  1. 开始一个Event loop
  2. 执行栈从tasks queue中取任务,并执行。
  3. 执行完后,执行栈清空
  4. 执行栈从microtasks queue中取任务执行
  5. 执行完成,执行栈清空
  6. 判断microtasks queue是否还有任务,有则重复步骤3。
  7. 进入 Update the rendering(更新渲染)阶段
  8. Event loop结束

流程图以下:浏览器

clipboard.png

再仔细理解一下开头列出的代码:网络

for (macroTask of macroTaskQueue) {
    // 1. Handle current MACRO-TASK
    handleMacroTask();
      
    // 2. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

VUE与EVENT_LOOP

为何使用Microtask queue

对于VUE这类web2.0框架而言,最主要作的应该仍是把对data的修改映射到DOM上。若是只修改一个数据,就刷新一次DOM,那么在一个同步过程当中,同时修改好几个数据,必然会致使屡次渲染。这确定是不可取的。
从上面的event loop咱们了解到,一个event loop对应一次render。理想情况固然就是,一次event loop产生的全部改动最好再render以前将DOM都先更新好。这样在一个周期中就能够只render一次。app

从这点需求出发很容易发现,microtask queue很符合要求。框架

  1. microtask queue确定在render ui以前执行完
  2. 不像task queue存在多个,microtask只存在一个queue。
  3. 最关键的是: microtask queue只有都清空了才能进入下一步,不管queue里是何时塞进来的。

实际代码中,不论是鼠标点击仍是键盘输入或是网络时间,触发了哪些方法,这些触发均可以当作开启一个event loop。这些触发形成的任何修改都放到microtask queue中,就能够保证在这一轮的evnet loop走到render ui时能够拿到最新的DOM。

要说明的是,这里的render并非维护虚拟DOM,也不是把虚拟DOM的变化投射到真实DOM上。而是将真实DOM更新到UI的过程。

这么说是由于:Event Loop 并非在 ECMAScript 标准中定义的,而是在 HTML 标准中定义的:

To coordinate events, user interaction, scripts, rendering, networking, and so forth...

在 JavaScript Engine 中(以 V8 为例),只是实现了 ECMAScript 标准,而并不关心什么 Event Loop。也就是说 Event Loop 是属于 JavaScript Runtime 的,是由宿主环境提供的(好比浏览器)。
浏览器可不会关心什么虚拟DOM。只负责DOM改变后渲染UI。

为何不用task queue

同上: 在开启一个event loop后,若是将任务放到task queue中,那么这个task任务只会在本轮Event loop结束后才会执行,并开启新一轮event loop。这无疑会致使两次render UI。
实际上,尤大为了修复一些bug,曾经将VUE.nexttick用task queue实现。可是致使了很明显的性能问题。
能够看看两个列子: 例一 , 例二
两个fiddle的实现如出一辙,就是让那个绝对定位的黄色元素起到一个fixed定位的效果:绑定scroll事件,每次滚动的时候,计算当前滚动的位置并更改到那个绝对定位元素的top属性上去。你们本身试试滚动几下,对比下效果,你就会发现第一个fiddle中的黄元素是稳定不动的,fixed很好。然后一个fiddle中就有问题了,黄色元素上下晃动,彷佛跟不上咱们scroll的节奏,总要慢一点,虽然最后停下滚动时位置是对的。

上述两个例子实际上是在这个issue中找到的,第一个jsfiddle使用的版本是Vue 2.0.0-rc.6,这个版本的nextTick实现是采用了MO,然后由于IOS9.3的WebView里的MO有bug,因而尤雨溪更改了实现,换成了window.postMessage,也就是后一个fiddle所使用的Vue 2.0.0-rc.7。后来尤雨溪了解到window.postMessage是将回调放入的macrotask 队列。这就是问题的根源了。

参考:

  1. Tasks, microtasks, queues and schedules
  2. 深刻理解 JavaScript Event Loop
  3. Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心!
相关文章
相关标签/搜索