各位!久等了。
時隔五個月,我又開始更新公眾號博客了。最近自己有點懶散,造成這麼長一段時間公眾號和博客斷更了,在這裡對關注我的各位同學們說聲抱歉!!
這幾個月一直忙於工作,休息時間是一半追劇,一半用來去提升自己了,然後看點書玩玩基金什麼的,荒廢了不少時間。最近也在一直調整自己的狀態,覺得還是要更新起來,把自己學到的一些東西分享給各位同學,這樣對於一個技術人來說,是一件有意義的事情!
上面扯了半天白話,說了最近自己的狀況,還是快點進入主題吧。
new Vue({ methods: { example: function () { this.message = 'changed' this.$nextTick(function () { this.doSomethingElse() }) } }})上面我用了官網的例子和解釋,我覺得例子還沒完全說明白nextTick解決了什麼樣的問題,所以我覺得我需要補充一下我自己的理解。
大家都知道,在vue的created生命周期裡面,如果你需要在這個生命周期鉤子裡面操作dom是不行的,因為這個鉤子執行的時候dom並沒有渲染到頁面上,所以會直接報錯。這裡有的同學就說,如果我必須要在這裡操作dom呢?那這個時候就到了$nextTick登場的時候,根據官方的解釋和例子也可以證明這一點,nextTick的參數回調函數執行的時候就是dom已經渲染到了頁面,掛載好了,並且把當前this綁定到了當前的實例上面去了,這個時候就可以獲取到更新後的dom元素去操作dom。
其實真正的用意,並不是為了解決這個,其實是為了解決響應式更新的問題。我們都知道,vue是響應式的,什麼是響應式?簡單說,就是數據變了,視圖變,視圖變了,數據變。這樣一來就有一個問題,不知道大家發現了沒,如果沒發現,我們就先看一下下面這段代碼<template> <div> {{a}} {{b}} {{c}} </div></template>new Vue({ data () { return { a: 1, b: 2, c: 3 } }, created () { this.a = 2 this.b = 3 this.c = 4 }})上面這段代碼看似很簡單,對不對。但是這裡有一個很有趣的事,大家先想一想,我們說了vue是響應式的,那當我們this.a做重新賦值的時候是不是就把a的值進行修改了,那修改了是不是就應該要觸發頁面的更新,把最新的值顯示到頁面上去,按理來說應該會更新三次對吧,因為我們先修改了a,然後修改b,最後修改的是c,但是結果告訴我們頁面只更新了一次,為什麼呢?說到底,這是因為nextTick方法在裡面作怪,我覺得這個做法非常聰明,這樣可以將性能損失降到最小。這裡只更新一次是因為在源碼上,在收集更新Wathcer時將更新通過nextTick方法做了延遲執行,所以當更新的時候,是先把所有的更新Wathcer收集了起來,然後調用nextTick方法做延遲更新的執行,這樣一來,賦值操作就是在前一直更新數據,更新就會不斷的添加更新Wathcer到隊列中,最後只需要拿出隊列中的所有更新Wathcer去進行挨個更新,這樣一來就會是現在看到的這樣,在賦值的時候並不會馬上更新反應到頁面,而是會先等你的賦值都做完後,最後統一更新,這樣就解決了頁面更新頻率問題。
下面我們來看看官方的對應源碼,來證明一下我說的是不是這樣的。代碼位置在github對應:vue/blob/dev/src/core/observer/scheduler.js文件中,大家可以去看一下export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } if (!waiting) { waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) } }}上面說完了使用,下面就是乾貨了,我們來說說nextTick的原理實現,分析一下源碼是怎麼寫的。源碼分析,源碼對應位置在github的vue/blob/dev/src/core/util/next-tick.js文件下面
import { noop } from 'shared/util'import { handleError } from './error'import { isIE, isIOS, isNative } from './env'export let isUsingMicroTask = false
const callbacks = [] let pending = false
function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() }}
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) { const 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]')) {
let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) }} else { timerFunc = () => { setTimeout(flushCallbacks, 0) }}
export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) }}最後需要說一下重點的一個變量,就是timerFunc變量,它的最後值決定當前nextTick異步實現的處理方式,代碼會挨個順序判斷兼容情況,最後選擇一個最適合的方式:Promise,MutationObserver, setImmediate,setTimeout長按識別二維碼
關注「前端技術專欄」加星標
每天給您推送最新原創技術文章
好看,幫點擊在看❤️