您现在的位置是:网站首页> 编程资料编程资料
vue中的任务队列和异步更新策略(任务队列,微任务,宏任务)_vue.js_
2023-05-24
590人已围观
简介 vue中的任务队列和异步更新策略(任务队列,微任务,宏任务)_vue.js_
事件循环
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,Event Loop 的方案应用而生。
Event Loop 包含两类:
- 一类是基于 Browsing Context
- 一种是基于 Worker
二者的运行是独立的,也就是说,每一个 JavaScript 运行的"线程环境"都有一个独立的 Event Loop,每一个 Web Worker 也有一个独立的 Event Loop。
任务队列
vue 数据驱动视图是数据改变,视图异步等待所有数据变化完成,统一进行视图更新。
既然是异步就有顺序和优先级, 异步任务队列是那种顺序执行 ?
tip: 微任务优先级高于宏任务 MDN 介绍
任务队列主要分为两种:
1、microtasks(微任务):
Promise:ES6的异步处理方案process.nextTick(vue.nextTick):下轮tick更新机制,Mutation Observer API: DOM改变 监听 API
2、macrotasks(宏任务也称任务):
setTimeout(): 延时器setInterval(): 计时器setImmediate:node.js 回调函数延迟执行,process.nextTicl() 方法十分类似
process.nextTick()中的回调函数执行的优先级要高于setImmediate().这里的原因在于事件循环对观察者的检查是有先后顺序的,process.nextTick()属于idle观察者,setImmediate()属于check观察者。在每一个轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。
在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果则是保存在链表中。在行为上,process.nextTick()在每轮循环中会将该数组中的回调函数全部执行完,而setImmediate()在每轮循环中执行链表中的一个回调函数
I/O:系统IO(input/output)UI render:页面渲染requestAnimationFrame():异步动画渲染 ,浏览器调用指定的函数以在下次重绘之前更新动画
如何理解微任务和宏任务?
创造代码的人也是人,他们的灵感多数来自于生活。我们这里打个比方(朴灵老师也这样比喻),javascript处理异步就像去餐馆吃饭,服务员会先为顾客下单,下完单把顾客点的单子给后厨让其准备,然后就去服务下一位顾客,,而不是一直等待在出餐口。
javascript将顾客下单的行为进行了细分。无外乎两种酒水类和非酒水类。对应着我们javascript中的macroTask和microTask。
但是在不同场景下的步骤是不一样的,就像西餐和中餐。西餐划分的非常详细:头盘->汤->副菜->主菜->蔬菜类菜肴->甜品->咖啡,中餐就相对简单许多:凉菜->热菜->汤。
任务队列,下面看几个示例, 输出内容便是执行顺序:
任务队列,下面看几个示例, 输出内容便是执行顺序:
setTimeout(()=>{ console.log('1') Promise.resolve().then(function() { console.log('2') }) }, 0) setTimeout(()=>{ console.log('3') Promise.resolve().then(function() { console.log('4') }) }, 0) setTimeout(function() {console.log('6')}, 0) requestAnimationFrame(function(){ console.log('5') }) setTimeout(function() {console.log('7')}, 0) new Promise(function executor(resolve) { console.log('1') resolve() console.log('2') }).then(function() { console.log('4') }) console.log('3') console.log('1'); setTimeout(() => { console.log('5'); process.nextTick(() => console.log('6')); }, 0); process.nextTick(() => { console.log('3'); process.nextTick(() => console.log('4')); }); console.log('2'); 这样就可以理解Vue的异步更新策略运行机制
created() { this.textDemo() }, methods: { textDemo() { console.log(1) setTimeout(() => { // macroTask console.log(4) setTimeout(() => { // macroTask console.log(8) }, 0) this.$nextTick(() => { // microTask console.log(5) }) Promise.resolve().then(function() { // microTask console.log(7) }) this.$nextTick(() => { // microTask console.log(6) }) }, 0) this.$nextTick(() => { // microTask console.log(3) }) console.log(2) }, } 到此我们已经知道代码是如何运行的了, 如果你想要更深入理解, 请继续向下阅读。
深究Vue异步更新策略原理
我们先看一个示例:
{{test}}
通过上面示例可以看出 Vue是异步执行DOM更新, 更新会缓冲到队列中, 在nextTick 集中刷新队列并执行实际 (已去重的) 工作
当然新版 this.$nextTick 有一些变化 。从Vue 2.5+开始,抽出来了一个单独的文件next-tick.js来执行它。

其大概的意思就是:在Vue 2.4之前的版本中,nextTick 几乎都是基于 microTask 实现的(具体可以看文章nextTick一节),但是由于 microTask 的执行优先级非常高,在某些场景之下它甚至要比事件冒泡还要快,就会导致一些诡异的问题;但是如果全部都改成 macroTask,对一些有重绘和动画的场景也会有性能的影响。所以最终 nextTick 采取的策略是默认走 microTask,对于一些DOM的交互事件,如 v-on绑定的事件回调处理函数的处理,会强制走 macroTask。
具体做法就是,在Vue执行绑定的DOM事件时,默认会给回调的handler函数调用withMacroTask方法做一层包装,它保证整个回调函数的执行过程中,遇到数据状态的改变,这些改变而导致的视图更新(DOM更新)的任务都会被推到macroTask。
源码:
function add$1 (event, handler, once$$1, capture, passive) { handler = withMacroTask(handler); if (once$$1) { handler = createOnceHandler(handler, event, capture); } target$1.addEventListener( event, handler, supportsPassive ? { capture: capture, passive: passive } : capture ); } /** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a Task instead of a MicroTask. */ function withMacroTask (fn) { return fn._withTask || (fn._withTask = function () { useMacroTask = true; var res = fn.apply(null, arguments); useMacroTask = false; return res }) } 最后,写一段DEMO验证一下 :
在Vue 2.5+中,这段代码的输出顺序是1 - 2 - 3 - 4, 而 Vue 2.4 和 不通过DOM 输出 2 - 3 - 1 - 4。nextTick执行顺序的差异正好说明了上面的改变。
tips: 所以这里需要留意遇到DOM操作, 同步执行受阻或者节点内容未及时更新可以使用 this.$nextTick 等待一下在执行下面的操作。
毫无悬念,这样肯定是会报错的:
Uncaught (in promise) DOMException: The element has no supported sources.
原因就在于audio.play()是同步的,而这个时候DOM更新是异步的,src属性还没有被更新,结果播放的时候src属性为空,就报错了。
解决办法就是在play的操作加上this.$nextTick()。
this.$nextTick(function() { this.$refs.audio.play(); }); 异步更新有什么好处?
{{test}}
上面的例子非常直观,可以这么理解当数据更新同步操作DOM会出现频繁渲染视图造成页面卡顿,极端的消耗资源。所以异步更新大大提升了性能, 并且数据更新很高效体验并没有降低。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
相关内容
- Vue3中slot插槽基本使用_vue.js_
- Vue3+ts+setup getCurrentInstance使用时遇到的问题以及解决办法_vue.js_
- vue中的computed 和 vm.$data 原理解析_vue.js_
- Vue3系列教程之插槽slot详解_vue.js_
- 使用vue3+ts+setup获取全局变量getCurrentInstance的方法实例_vue.js_
- vuex 设计思路和实现方式_vue.js_
- vue3+ts如何通过lodash实现防抖节流详解_vue.js_
- vue项目中更改名字和图标的简单实现步骤_vue.js_
- vue获取input值的三种常用写法_vue.js_
- Vue中的table表单切换实现效果_vue.js_
