Vue学习系列之2、nextTick

1、事件循环

一、提问

首先,都2021了,你们对taskmircotasktask queue这些概念都很清楚了。但你们可否回答如下的问题:javascript

  1. 任务队列是一个队列吗,task是否会分优先级?什么样的task优先级更高?
  2. requestAnimateFrame在什么阶段执行?requestIdleCallback呢?
  3. 事件循环浏览器渲染之间有什么关系?

ok,若是这些问题你都能回答上来,大佬请私信我您的微信,我们♂深刻交流一下♀😂;若是有不清楚,也别慌,这篇文章就是为了把这些东西都搞明白。css

如下的结论所有都根据HTML5 规范,英文好的小伙伴能够直接去看html

二、定义

2.1 事件循环

为了协调事件、用户交互、脚本、渲染、网络等,UA必须使用事件循环。每一个UA都有一个关联的事件循环,这是该UA独有的。vue

这里的UA指的是user Agnet,其包括了代码运行所需的环境——ECMAScript执行上下文、执行对战、执行线程等,可是执行线程并不属于UA,因此有可能出现一个线程协同调度着多个事件循环。java

这样说其实就很复杂了,咱们能够简单认为一个tab页面对应一个事件循环便可。react

2.2 任务队列、任务

An event loop has one or more task queues. A task queue is a Set of tasks.git

Task queues are sets,not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.github

一个事件循环有一个或多个任务队列,任务队列是多个任务的Setweb

这里解决了咱们的第一个问题:任务队列并非一个队列,由于在事件循环执行流程的第一步,是从选中的任务队列中拿出第一个能够执行的任务,而不是出队第一个任务。算法

2.3 不止一个任务队列

For example, a user agent could have one task queue for mouse and key events to which the user interaction task source is associated, and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.

刚刚上面也提到了,一个任务循环会有多个任务队列,每一个任务队列中保存着不一样类型的任务,好比:

  1. 鼠标、键盘这类的用户交互任务
  2. 其余的任务,好比postMessage、setTimeout、setIntervel、http

浏览器会在按照任务顺序的前提下,将四分之三的优先级分配给用户交互事件,来保证响应的及时性,同时也不会把其他的任务饿死

也就是由于这个缘由,在Vue中产生了这个issue,这个问题的具体缘由其实就是由于当时Vue的nextTick的实现使用的是postMessage,它做为一个task优先级很低,当遇到浏览器的滚动时,事件循环优先选择了的用户交互的任务队列,致使postMessage中的渲染函数迟迟没有执行,视图没有更新,在以后的版本中,尤大把nextTick的实现改成了Mutation ObserverPromise.resolve的方案。

2.4 事件循环流程

  1. 从选中的任务队列中取出一个宏任务执行

  2. 检查微任务队列,执行微任务,清空微任务队列,在执行过程当中产生的微任务均在本次事件循环中执行完毕

  3. 检查是否须要更新,这里有个概念叫作Rendering opportunities,用来判断本次task执行完毕后是否须要进行更新,也就是说并非完成一次task就要进行视图的更新,颇有多是在屡次事件循环后,才进行一次视图更新

    • 一般来讲视图更新的间隔是固定的,对应着屏幕刷新的频率60fps,也就是说每次视图更新的间隔是在16.7ms,当页面性能没法维持在这个频率的时候,浏览器会将频率降低到30fps,以免丢帧。

    • 当浏览器上下文不可见时,频率甚至能够降到4fps。这里的浏览器上下文指的就是页面,不可见的状况好比iframe、当前tab不在此页面等状况

    • 当浏览器认定当前修改渲染,不会有任何的改变,而且map of animation frame callbacks 为空,也就是没有调用requestAnimationFrame时,也会跳过渲染

    • 最后还有一种状况,就是当浏览器认为因为其余缘由最好跳过更新渲染(主要是为了合并定时器回调)。

      This step enables the user agent to prevent the steps below from running for other reasons, for example, to ensure certain tasks are executed immediately after each other, with only microtask checkpoints interleaved (and without, e.g., animation frame callbacks interleaved). Concretely, a user agent might wish to coalesce timer callbacks together, with no intermediate rendering updates.

    • 当出现上述的任意一种状况是,跳过3.x的所有步骤。

      3.1 若是页面出现了窗口大小改变,直接执行resize方法

      3.2 若是页面出现了滚动,直接执行scroll方法

      3.3 执行requestAnimationFrame的回调

      3.4 执行Intersection Observer的回调

      3.5 从新绘制用户界面

  4. 启动空闲期算法,也就是执行requestIdleCallback的回调。

对于resizescroll来讲,并不会等到从新绘制用户界面这一步,这样会有不少的延迟,在CSSOM VIEW中有提到,若是是resize,浏览器会当即触发resize事件;scroll相对复杂一些,浏览器会初始化一个pending scroll event targets,当事件循环触发了scroll方法的时候,会将targets收集到的元素队列依次触发scroll事件,固然是冒泡的,若是是元素的scroll事件的话还会将document的事件取消掉。

task1.jpeg

以上就是基本的事件循环的流程,你们能够对看着图,对照的流程慢慢梳理。但愿你们看完以后能够完美回答,本文一开始的问题。

其中其实还有不少的细节,可是太过繁琐,我并无所有列出来,若是还想深刻了解的话,能够去看上文提到的HTML5的规范

2、Vue中的nextTick

一、 Vue的更新DOM是异步的

Vue文档中有这样一段描述,当Vue修改数据后,并不会当即触发watcher的更新,而是将其推入一个异步的队列中,这个队列将watcher进行排序,而且会异步执行这些watcher,固然这其中就包含更新DOM。

那咱们思考一下,这个所谓的异步应该处于哪一个阶段呢?答案是,微任务。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserver 和 setImmediate,若是执行环境不支持,则会采用 setTimeout(fn, 0) 代替。这也就是nextTick的实现。

二、举个🌰

<div id="example">{{message}}</div>
<script> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改数据  vm.$el.textContent === 'new message' // false  Vue.nextTick( function () { vm.$el.textContent === 'new message' // true  } ) </script>
复制代码
  1. 事件循环正在执行宏任务

  2. vm.message = 'new message'触发setter,将render Watcher放入异步队列,Vue内部调用了nextTick将异步队列放入微任务队列 nt-2.png

  3. 因为DOM没有更新因此,第一次判断textContent的值是123,因此是false nT-1.png

  4. 用户调用了nextTick将回调推入微任务队列,这里注意并非将函数推入了异步队列,异步队列中只有watcher,且将队列中的watcher所有执行完,是在一个微任务中完成的。 nt-3.png

  5. Task执行完毕,开始执行清空微任务队列,第一个微任务就是将异步队列中的watcher更新一遍 nt-4.png

  6. watcher所有更新,DOM更新,这里注意,是DOM更新而不是视图更新,由于是否更新视图是浏览器来决定的。若是这里有点蒙,请再看一遍事件循环的流程。 nt-5.png

  7. 执行下一个微任务,因为此时DOM已经更新,因此判断结果为true。 nt-6.png

3、延伸阅读

相关文章
相关标签/搜索