vue中的任務(wù)隊(duì)列和異步更新策略(任務(wù)隊(duì)列,微任務(wù),宏任務(wù))
事件循環(huán)
JavaScript 語(yǔ)言的一大特點(diǎn)就是單線程,也就是說(shuō),同一個(gè)時(shí)間只能做一件事。
為了協(xié)調(diào)事件、用戶交互、腳本、UI 渲染和網(wǎng)絡(luò)處理等行為,防止主線程的不阻塞,Event Loop 的方案應(yīng)用而生。
Event Loop 包含兩類:
- 一類是基于 Browsing Context
- 一種是基于 Worker
二者的運(yùn)行是獨(dú)立的,也就是說(shuō),每一個(gè) JavaScript 運(yùn)行的"線程環(huán)境"都有一個(gè)獨(dú)立的 Event Loop,每一個(gè) Web Worker 也有一個(gè)獨(dú)立的 Event Loop。
任務(wù)隊(duì)列
vue 數(shù)據(jù)驅(qū)動(dòng)視圖是數(shù)據(jù)改變,視圖異步等待所有數(shù)據(jù)變化完成,統(tǒng)一進(jìn)行視圖更新。
既然是異步就有順序和優(yōu)先級(jí), 異步任務(wù)隊(duì)列是那種順序執(zhí)行 ?
tip: 微任務(wù)優(yōu)先級(jí)高于宏任務(wù) MDN 介紹
任務(wù)隊(duì)列主要分為兩種:
1、microtasks(微任務(wù)):
Promise
:ES6的異步處理方案process.nextTick(vue.nextTick)
:下輪tick更新機(jī)制,Mutation Observer API
: DOM改變 監(jiān)聽(tīng) API
2、macrotasks(宏任務(wù)也稱任務(wù)):
setTimeout()
: 延時(shí)器setInterval()
: 計(jì)時(shí)器setImmediate
:node.js 回調(diào)函數(shù)延遲執(zhí)行,process.nextTicl() 方法十分類似
process.nextTick()中的回調(diào)函數(shù)執(zhí)行的優(yōu)先級(jí)要高于setImmediate().這里的原因在于事件循環(huán)對(duì)觀察者的檢查是有先后順序的,process.nextTick()屬于idle觀察者,setImmediate()屬于check觀察者。在每一個(gè)輪循環(huán)檢查中,idle觀察者先于I/O觀察者,I/O觀察者先于check觀察者。
在具體實(shí)現(xiàn)上,process.nextTick()的回調(diào)函數(shù)保存在一個(gè)數(shù)組中,setImmediate()的結(jié)果則是保存在鏈表中。在行為上,process.nextTick()在每輪循環(huán)中會(huì)將該數(shù)組中的回調(diào)函數(shù)全部執(zhí)行完,而setImmediate()在每輪循環(huán)中執(zhí)行鏈表中的一個(gè)回調(diào)函數(shù)
I/O
:系統(tǒng)IO(input/output)UI render
:頁(yè)面渲染requestAnimationFrame()
:異步動(dòng)畫(huà)渲染 ,瀏覽器調(diào)用指定的函數(shù)以在下次重繪之前更新動(dòng)畫(huà)
如何理解微任務(wù)和宏任務(wù)?
創(chuàng)造代碼的人也是人,他們的靈感多數(shù)來(lái)自于生活。我們這里打個(gè)比方(樸靈老師也這樣比喻),javascript處理異步就像去餐館吃飯,服務(wù)員會(huì)先為顧客下單,下完單把顧客點(diǎn)的單子給后廚讓其準(zhǔn)備,然后就去服務(wù)下一位顧客,,而不是一直等待在出餐口。
javascript將顧客下單的行為進(jìn)行了細(xì)分。無(wú)外乎兩種酒水類和非酒水類。對(duì)應(yīng)著我們javascript中的macroTask和microTask。
但是在不同場(chǎng)景下的步驟是不一樣的,就像西餐和中餐。西餐劃分的非常詳細(xì):頭盤(pán)->湯->副菜->主菜->蔬菜類菜肴->甜品->咖啡,中餐就相對(duì)簡(jiǎn)單許多:涼菜->熱菜->湯。
任務(wù)隊(duì)列,下面看幾個(gè)示例, 輸出內(nèi)容便是執(zhí)行順序:
任務(wù)隊(duì)列,下面看幾個(gè)示例, 輸出內(nèi)容便是執(zhí)行順序:
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的異步更新策略運(yùn)行機(jī)制
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) }, }
到此我們已經(jīng)知道代碼是如何運(yùn)行的了, 如果你想要更深入理解, 請(qǐng)繼續(xù)向下閱讀。
深究Vue異步更新策略原理
我們先看一個(gè)示例:
<template> ? <div> ? ? <div ref="test">{{test}}</div> ? ? <button @click="handleClick">tet</button> ? </div> </template>
<script> export default { ? ? data () { ? ? ? ? return { ? ? ? ? ? ? test: 'begin' ? ? ? ? }; ? ? }, ? ? methods () { ? ? ? ? handleClick () { ? ? ? ? ? ? this.test = 'end'; ? ? ? ? ? ? console.log(this.$refs.test.innerText);// 結(jié)果輸出 begin ? ? ? ? } ? ? } } </script>
通過(guò)上面示例可以看出 Vue是異步執(zhí)行DOM更新, 更新會(huì)緩沖到隊(duì)列中, 在nextTick 集中刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作
當(dāng)然新版 this.$nextTick 有一些變化 。從Vue 2.5+開(kāi)始,抽出來(lái)了一個(gè)單獨(dú)的文件next-tick.js來(lái)執(zhí)行它。
其大概的意思就是:在Vue 2.4之前的版本中,nextTick 幾乎都是基于 microTask 實(shí)現(xiàn)的(具體可以看文章nextTick一節(jié)),但是由于 microTask 的執(zhí)行優(yōu)先級(jí)非常高,在某些場(chǎng)景之下它甚至要比事件冒泡還要快,就會(huì)導(dǎo)致一些詭異的問(wèn)題;但是如果全部都改成 macroTask,對(duì)一些有重繪和動(dòng)畫(huà)的場(chǎng)景也會(huì)有性能的影響。所以最終 nextTick 采取的策略是默認(rèn)走 microTask,對(duì)于一些DOM的交互事件,如 v-on綁定的事件回調(diào)處理函數(shù)的處理,會(huì)強(qiáng)制走 macroTask。
具體做法就是,在Vue執(zhí)行綁定的DOM事件時(shí),默認(rèn)會(huì)給回調(diào)的handler函數(shù)調(diào)用withMacroTask方法做一層包裝,它保證整個(gè)回調(diào)函數(shù)的執(zhí)行過(guò)程中,遇到數(shù)據(jù)狀態(tài)的改變,這些改變而導(dǎo)致的視圖更新(DOM更新)的任務(wù)都會(huì)被推到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 }) }
最后,寫(xiě)一段DEMO驗(yàn)證一下 :
<template> ?? ?<div> ?? ? ? ?<button @click="handleClick">change</button> ?? ?</div> </template>
<script> export default { ? ?created() { ? ? ? this.handleClick() // ?得出結(jié)果 : 2 3 1 4 ? }, ? ?methods: { ?? ? ? handleClick() { ?? ? ? ? setTimeout(() => { // macroTask ?? ? ? ? ? console.log(4) ?? ? ? ? }, 0) ?? ? ?? ? ? ? this.$nextTick(() => { // microTask ?? ? ? ? ? console.log(2) ?? ? ? ? }) ?? ? ?? ? ? ? Promise.resolve().then(function() { // microTask ?? ? ? ? ? console.log(1) ?? ? ? ? }) ?? ? ?? ? ? ? this.$nextTick(() => { // microTask ?? ? ? ? ? console.log(3) ?? ? ? ? }) ?? ? ? } ? ?} } </script>
在Vue 2.5+中,這段代碼的輸出順序是1 - 2 - 3 - 4, 而 Vue 2.4 和 不通過(guò)DOM 輸出 2 - 3 - 1 - 4。nextTick執(zhí)行順序的差異正好說(shuō)明了上面的改變。
tips: 所以這里需要留意遇到DOM操作, 同步執(zhí)行受阻或者節(jié)點(diǎn)內(nèi)容未及時(shí)更新可以使用 this.$nextTick 等待一下在執(zhí)行下面的操作。
<div id="example"> <audio ref="audio" :src="url"></audio> <span ref="url"></span> <button @click="changeUrl">click me</button> </div>
<script> const musicList = [ 'http://sc1.111ttt.cn:8282/2017/1/11m/11/304112003137.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3', 'http://sc1.111ttt.cn:8282/2017/1/11m/11/304112002493.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3', 'http://sc1.111ttt.cn:8282/2017/1/11m/11/304112004168.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3' ]; var vm = new Vue({ el: '#example', data: { index: 0, url: '' }, methods: { changeUrl() { this.index = (this.index + 1) % musicList.length this.url = musicList[this.index]; this.$refs.audio.play(); } } }); </script>
毫無(wú)懸念,這樣肯定是會(huì)報(bào)錯(cuò)的:
Uncaught (in promise) DOMException: The element has no supported sources.
原因就在于audio.play()是同步的,而這個(gè)時(shí)候DOM更新是異步的,src屬性還沒(méi)有被更新,結(jié)果播放的時(shí)候src屬性為空,就報(bào)錯(cuò)了。
解決辦法就是在play的操作加上this.$nextTick()。
this.$nextTick(function() { this.$refs.audio.play(); });
異步更新有什么好處?
<template> <div> <div>{{test}}</div> </div> </template>
<script> export default { data () { return { test: 0 }; }, mounted () { for(let i = 0; i < 1000; i++) { this.test++; } } } </script>
上面的例子非常直觀,可以這么理解當(dāng)數(shù)據(jù)更新同步操作DOM會(huì)出現(xiàn)頻繁渲染視圖造成頁(yè)面卡頓,極端的消耗資源。所以異步更新大大提升了性能, 并且數(shù)據(jù)更新很高效體驗(yàn)并沒(méi)有降低。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- vue在使用ECharts時(shí)的異步更新和數(shù)據(jù)加載詳解
- vue的狀態(tài)更新方式(異步更新解決)
- VUE異步更新DOM - 用$nextTick解決DOM視圖的問(wèn)題
- 詳解Vue的異步更新實(shí)現(xiàn)原理
- 淺談Vuejs中nextTick()異步更新隊(duì)列源碼解析
- vue中$nextTick的用法講解
- Vue中this.$nextTick的作用及用法
- Vue中的nextTick作用和幾個(gè)簡(jiǎn)單的使用場(chǎng)景
- Vue中this.$nextTick()的理解與使用方法
- 簡(jiǎn)單理解Vue中的nextTick方法
- vue2.0$nextTick監(jiān)聽(tīng)數(shù)據(jù)渲染完成之后的回調(diào)函數(shù)方法
- 深入理解Vue nextTick 機(jī)制
- Vue2異步更新及nextTick原理詳解
相關(guān)文章
使用Element-UI的el-tabs組件,瀏覽器卡住了的問(wèn)題及解決
這篇文章主要介紹了使用Element-UI的el-tabs組件,瀏覽器卡住了的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08深入理解Vue keep-alive及實(shí)踐總結(jié)
這篇文章主要介紹了深入理解Vue keep-alive及實(shí)踐總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08使用van-picker?動(dòng)態(tài)設(shè)置當(dāng)前選中項(xiàng)
這篇文章主要介紹了使用van-picker?動(dòng)態(tài)設(shè)置當(dāng)前選中項(xiàng)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10詳解VUE 定義全局變量的幾種實(shí)現(xiàn)方式
本篇文章主要介紹了VUE 全局變量的幾種實(shí)現(xiàn)方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06ElementUI實(shí)現(xiàn)el-table列寬自適應(yīng)的代碼詳解
這篇文章給大家介紹了ElementUI實(shí)現(xiàn)el-table列寬自適應(yīng)的詳細(xì)步驟,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01vue 列表頁(yè)跳轉(zhuǎn)詳情頁(yè)獲取id以及詳情頁(yè)通過(guò)id獲取數(shù)據(jù)
這篇文章主要介紹了vue 列表頁(yè)跳轉(zhuǎn)詳情頁(yè)獲取id以及詳情頁(yè)通過(guò)id獲取數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Vue網(wǎng)頁(yè)html轉(zhuǎn)換PDF(最低兼容ie10)的思路詳解
這篇文章主要介紹了Vue網(wǎng)頁(yè)html轉(zhuǎn)換PDF(最低兼容ie10)的思路詳解,實(shí)現(xiàn)此功能需要引入兩個(gè)插件,需要的朋友可以參考下2017-08-08