?🌈個人主頁:前端青山
🔥系列專欄:Vue篇
🔖人終將被年少不可得之物困其一生
依舊青山,本期給大家帶來vue篇專欄內容:vue中的$nextTick
目錄
🐋Vue中的$nextTick有什么作用?
🐋一、NextTick是什么
為什么要有nexttick
🐋二、使用場景
🐋三、實現原理
?$nextTick()
Vue中的$nextTick有什么作用?
一、NextTick是什么
官方對其的定義
在下次 DOM 更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM
什么意思呢?
我們可以理解成,Vue
在更新 DOM
時是異步執行的。當數據發生變化,Vue
將開啟一個異步更新隊列,視圖需要等隊列中所有數據變化完成之后,再統一進行更新
舉例一下
Html
結構
<div id="app"> {{ message }} </div>
構建一個vue
實例
const vm = new Vue({el: '#app',data: {message: '原始值'}
})
修改message
this.message = '修改后的值1'
this.message = '修改后的值2'
this.message = '修改后的值3'
這時候想獲取頁面最新的DOM
節點,卻發現獲取到的是舊值
console.log(vm.$el.textContent) // 原始值
這是因為message
數據在發現變化的時候,vue
并不會立刻去更新Dom
,而是將修改數據的操作放在了一個異步操作隊列中
如果我們一直修改相同數據,異步操作隊列還會進行去重
等待同一事件循環中的所有數據變化完成之后,會將隊列中的事件拿來進行處理,進行DOM
的更新
為什么要有nexttick
舉個例子
{{num}}
for(let i=0; i<100000; i++){num = i
}
如果沒有 nextTick
更新機制,那么 num
每次更新值都會觸發視圖更新(上面這段代碼也就是會更新10萬次視圖),有了nextTick
機制,只需要更新一次,所以nextTick
本質是一種優化策略
二、使用場景
如果想要在修改數據后立刻得到更新后的DOM
結構,可以使用Vue.nextTick()
第一個參數為:回調函數(可以獲取最近的DOM
結構)
第二個參數為:執行函數上下文
// 修改數據
vm.message = '修改后的值'
// DOM 還沒有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {// DOM 更新了console.log(vm.$el.textContent) // 修改后的值
})
組件內使用 vm.$nextTick()
實例方法只需要通過this.$nextTick()
,并且回調函數中的 this
將自動綁定到當前的 Vue
實例上
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {console.log(this.$el.textContent) // => '修改后的值'
})
$nextTick()
會返回一個 Promise
對象,可以是用async/await
完成相同作用的事情
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
await this.$nextTick()
console.log(this.$el.textContent) // => '修改后的值'
三、實現原理
callbacks
也就是異步操作隊列
callbacks
新增回調函數后又執行了timerFunc
函數,pending
是用來標識同一個時間只能執行一次
export function nextTick(cb?: Function, ctx?: Object) {let _resolve;
?// cb 回調函數會經統一處理壓入 callbacks 數組callbacks.push(() => {if (cb) {// 給 cb 回調函數執行加上了 try-catch 錯誤處理try {cb.call(ctx);} catch (e) {handleError(e, ctx, 'nextTick');}} else if (_resolve) {_resolve(ctx);}});
?// 執行異步延遲函數 timerFuncif (!pending) {pending = true;timerFunc();}
?// 當 nextTick 沒有傳入函數參數的時候,返回一個 Promise 化的調用if (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve;});}
}
timerFunc
函數定義,這里是根據當前環境支持什么方法則確定調用哪個,分別有:
Promise.then
、MutationObserver
、setImmediate
、setTimeout
通過上面任意一種方法,進行降級操作
export let isUsingMicroTask = false
if (typeof Promise !== 'undefined' && isNative(Promise)) {//判斷1:是否原生支持Promiseconst p = Promise.resolve()timerFunc = () => {p.then(flushCallbacks)if (isIOS) setTimeout(noop)}isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {//判斷2:是否原生支持MutationObserverlet counter = 1const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {//判斷3:是否原生支持setImmediatetimerFunc = () => {setImmediate(flushCallbacks)}
} else {//判斷4:上面都不行,直接用setTimeouttimerFunc = () => {setTimeout(flushCallbacks, 0)}
}
無論是微任務還是宏任務,都會放到flushCallbacks
使用
這里將callbacks
里面的函數復制一份,同時callbacks
置空
依次執行callbacks
里面的函數
function flushCallbacks () {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}
?$nextTick()
綁定在實例上的 nextTick() 函數。
和全局版本的 nextTick()
的唯一區別就是組件傳遞給 this.$nextTick()
的回調函數會帶上 this
上下文,其綁定了當前組件實例。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>nextTick</title>
</head>
<body><div id="app"><div ref="oDiv">{{ count }}</div><button @click="add">加1</button></div>
</body>
<script src="../lib/vue.global.js"></script>
<script>Vue.createApp({data () {return {count: 10}},methods: {add () {this.count++console.log(1, this.count) // 11// 獲取dom節點真實的值console.log(2, this.$refs.oDiv.innerHTML) // 10
?// 借助于 nextTick 函數 獲取真實的DOM的值// 上啦加載 - 滾動條滾動到一定距離,加載數據// 瀑布流布局時,實時獲取每一列的高度,將下一個數據插入到高度最低的那一列this.$nextTick(() => {console.log(3, this.$refs.oDiv.innerHTML) // 11})}}}).mount('#app')
</script>
</html>
小結:
-
把回調函數放入callbacks等待執行
-
將執行函數放到微任務或者宏任務中
-
事件循環到了微任務或者宏任務,執行函數依次執行callbacks中的回調