

Vue2异步更新及nextTick原理 - 柏成
source link: https://www.cnblogs.com/burc/p/17267130.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

vue 官网中是这样描述 nextTick 的
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,可以获取更新后的 DOM。
在学习 nextTick 是如何实现之前,我们要先了解下 JavaScript 的执行机制
JavaScript 执行机制
浏览器是多线程的,例如GUI渲染线程、JS引擎线程、事件监听线程等。。。
javascript 执行机制就是借用浏览器的多线程机制,再基于 Event Loop 事件循环机制实现的。其实现了单线程异步效果
Event Loop 步骤大致如下:
- 浏览器加载页面时,除了开辟堆栈内存外,还会创建两个队列
- Web API:任务监听队列,监测异步任务是否可以执行
- Task Queue:任务队列,分为异步宏任务队列和异步微任务队列
- 当主线程自上而下执行代码过程中,如果遇到异步代码,则把异步任务放到 Web API 中去监听
- 浏览器会开辟新的线程去监听是否可以执行
- 不会阻碍主线程的渲染,它会继续向下执行同步代码
- 当异步任务被监测为可以执行了(有了运行结果),也不会立即去执行,而是在 task queue 中放置一个事件,排队等待执行
- 根据微任务还是宏任务,放在不同的队列中
- 谁先进来排队的,谁在各自队伍的最前面
- 执行栈中的所有同步任务执行完毕,主线程空闲下来,此时会去 task queue 中把正在排队的事件,按照顺序取出来,进入主线程执行
- 微任务优先级比较高。当执行栈为空时,先去执行微任务队列中的事件,直到微任务队列为空,才会去执行宏任务队列中的事件
- 上述过程会不断重复,也就是常说的事件循环(Event Loop)
task 又分为宏任务(macro task)和微任务(micro task)两大类,在浏览器环境中
- 常见的 macro task 有 script(整体代码)、
setTimeout/setInterval/setImmediate
、XMLHttpRequest/fetch
,DOM事件(如鼠标点击、滚动页面、放大缩小等),渲染事件(解析 DOM、计算布局、绘制) - 常见的 micro task 有
Promise.then/catch/finally
、async/await
、MutationObserver
需要注意的是!!!如果处理微任务的过程中有新的微任务添加进来了,添加的速度一直比执行快,则永远执行微任务
下面的代码永远不会打印宏任务输出
function macroFn(){ setTimeout(() => { console.log('>>>>MA') },0) } function microFn(){ Promise.resolve().then(() => { console.log('mi') microFn() }) } macroFn() microFn()
nextTick实现原理
vue2.7 源码中,有一个单独的文件src/core/util/next-tick.js
去维护 nextTick,有兴趣的同学可以自行去观看
vue2.7 源码中,nextTick并没有直接使用某个 API ,而是采用了优雅降级的方案去实现异步更新
在内部会尝试使用原生的Promise.then (IE不支持)
、MutationObserver
和 setImmediate (高版本IE专享)
,如果执行环境还不支持的话,则会采用 setTimeout(fn, 0)
需要注意的是,我们维护了一个 callbacks,用于存储 nextTick 回调
这样就保证了在同一个 tick 内多次调用 nextTick,只需创建一个异步任务,就可以依次执行 callbacks 中的所有 nextTick 回调。而不是去开启多个异步任务去处理。
let callbacks = [] // 存储 nextTick 回调 let waiting = false // 防抖 // 按照顺序依次执行 callbacks 中的方法 function flushCallbacks() { let cbs = callbacks.slice(0) waiting = false callbacks = [] cbs.forEach(cb => cb()) } let timerFunc; if (Promise) { timerFunc = () => { Promise.resolve().then(flushCallbacks) } }else if(MutationObserver){ let observer = new MutationObserver(flushCallbacks); // 这里传入的回调是异步执行的 let textNode = document.createTextNode(1); observer.observe(textNode,{ characterData:true }); timerFunc = () => { textNode.textContent = 2; } }else if(setImmediate){ timerFunc = () => { setImmediate(flushCallbacks); } }else{ timerFunc = () => { setTimeout(flushCallbacks); } } export function nextTick(cb) { callbacks.push(cb) // 维护 nextTick 中的 cakllback 方法 if (!waiting) { timerFunc() waiting = true } }
vue 内部的异步更新渲染也使用了 nextTick
在 Watcher 类的 update 更新方法中,我们调用了 queueWatcher 异步队列更新方法,该方法在 vue2.7源码中的 src/core/util/scheduler.js
文件中维护
import { queueWatcher } from './scheduler' class Watcher { ... // 重新渲染 update() { console.log('watcher-update') queueWatcher(this) // watcher 异步更新 } }
src/core/util/scheduler.js
import { nextTick } from '../util/next-tick' /** * @name QueueWatcher,内部 watcher 异步更新 * @decs 把当前的 watcher 暂存起来,在一个tick周期内,不管我们的 update 执行多少次,只会执行一轮刷新操作 */ let queue = [] let has = {} let pending = false // 防抖 function flushSchedulerQueue() { let flushQueue = queue.slice(0) queue = [] has = {} pending = false flushQueue.forEach(q => q.run()) // 在刷新的过程中可能还有新的 watcher,重新放到 queue 中 } // 在一个tick周期内,不管我们的 update 执行多少次,只会执行一轮刷新操作 export function queueWatcher(watcher) { const id = watcher.id if (!has[id]) { queue.push(watcher) has[id] = true if (!pending) { nextTick(flushSchedulerQueue) pending = true } } }
1. nexTick 是异步还是同步?
这个不能一概而论,nextTick 内部既有同步代码又有异步代码。
例如 维护 callbacks 队列是同步任务;执行队列中的方法是异步任务
2. nextTick 回调的执行是微任务还是宏任务?
针对 vue2.7 来说,nextTick并没有直接使用某个 API ,而是采用了优雅降级的方案去实现异步更新。
在内部会尝试使用原生的Promise.then (微任务)
、MutationObserver (微任务)
和 setImmediate (宏任务)
,如果执行环境还不支持的话,则会采用 setTimeout (宏任务)
可以理解为 99% 的场景下都是微任务,只有在不支持 Promise 和 MutationObserver API的浏览器中,才会是宏任务,例如 IE9 、IE10
3. 为什么要封装 nextTick?而不是使用某个具体的 API?
优雅降级。尽量使用微任务,尽可能缩短渲染周期
保证统一性。nextTick 可以暴露给用户,保证用户在修改数据之后立即使用这个方法,可以获取更新后的 DOM
this.name = 'libc' this.$nextTick(()=>{ console.log(document.querySelector('.user').innerHTML) });
__EOF__
Recommend
-
160
2017年09月24日 阅读 4289 从Vue.js源码看异步更新DOM策略及nextTick 因为对Vue.js很感兴趣,而且平时工作的技术栈...
-
48
写在前面 前段时间在写项目时对nextTick的使用有一些疑惑。在查阅各种资料之后,在这里总结一下Vue.js异步更新的策略以及nextTick的用途和原理。如有总结错误的地方,欢迎指出! 本文将从以下3点进行总结: 为什么Vue.js要异步更新视图?
-
11
上篇文章vue生命周期中我们说过一个句话,那就是mounted中并不会保证所有子组件都被挂载完成后再触发,因此当你希望视图完全渲染完成后再做某些事情时,请在mounted中使用$nextTick。那么$nextTick到底是干嘛用的,为什么能解决我们以上的问题。...
-
3
nextTick 是什么nextTick:根据官方文档的解释,它可以在 DOM 更新完毕之后执行一个回调函数,并返回一个 Promise(如果支持的话)// 修改
-
4
知其然且知其所以然,vue 作为目前最为主流的前端 MVVM 框架之...
-
8
vue2异步加载之前说过,vue3还是之前的方法,只是把 i18n.setLocaleMessage改为i18n.global.setLocaleMessage 但是本文还是详细说一遍: 为什么需要异步加载语言包 主要还是缩小提代码包,没有按需加载前,语言包内容太多
-
4
Vue.js 基本上遵循 MVVM(Model–View–ViewModel)架构模式,数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。 本文讲解一下 Vue 响应式系统的底层细节。 检测变化注意事项 Vue 2.0中...
-
5
Vue2模版编译(AST、Optimize 、Render) 在Vue $mount过程中,我们需要把模版编译成render函数,整体实现可以分为三部分:...
-
7
什么是虚拟DOM DOM是很慢的,其元素非常庞大,当我们频繁的去做 DOM更新,会产生一定的性能问题,我们可以直观感受一下 div元素包含的海量属性
-
5
【源码系列#04】Vue3侦听器原理(Watch) - 柏成 - 博客园 专栏分享:
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK