vue nextTick原理

vue nextTick原理

前面談到了vue2.x的響應(yīng)式原理,vue.js在視圖更新采用的是異步更新策略棺棵,我們來看看它是怎么做到的楼咳。

/** ? */

for(let i = 0; i < 100; i++) {
    this.count++;
}

/** 烛恤? */
在dom更新后執(zhí)行一些操作
this.$nextTick(fn)

先拋出兩個問題:

  1. for循環(huán)更新count數(shù)值母怜,dom會被更新100次嗎?
  2. nextTick是如何做到監(jiān)聽dom更新完畢的缚柏?

異步更新涉及到j(luò)s的運行機制苹熏,詳細的可看這里
event loop機制
這篇文章呢我們主要從源碼角度來分析nextTick的原理實現(xiàn)。

這是我們響應(yīng)式里面的watcher類

<!--觀察者Watcher類-->
class Watcher {
    constructor  () {
        Dep.target = this  // new Watcher的時候把觀察者存放到Dep.target里面
    }
    update () {
        queueWatcher(this) // 異步更新策略
    }
    run () {
        //  dom在這里執(zhí)行真正的更新
    }
}

watcher對象在進行更新執(zhí)行update币喧,內(nèi)部主要執(zhí)行了一個queueWatcher函數(shù)轨域,將watcher對象作為this進行傳遞,所以我們便從queueWatcher這個口子開始杀餐。

queueWatcher

queueWatcher函數(shù)在scheduler文件里面

/** queueWatcher函數(shù)*/
let has = {};
let queue = [];
let waiting = false;

function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 防止queue隊列wachter對象重復(fù)
  if (has[id] == null) {
    has[id] = true
    queue.push(watcher)
    
    // 傳遞本次的更新任務(wù)
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

/** flushSchedulerQueue函數(shù) */
function flushSchedulerQueue () {
    let watcher, id;
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        id = watcher.id;
        has[id] = null;
        // 執(zhí)行更新
        watcher.run();
    }
    // 更新完畢恢復(fù)標志位
    waiting = false;
}
  1. queue里面存放著我們本次要更新的watcher對象干发,queueWatcher函數(shù)做了一個判重操作,相同的watcher對象只會被加入到queue隊列一次史翘。
  2. flushSchedulerQueue函數(shù)依次調(diào)用了wacther對象的run方法執(zhí)行更新铐然。并作為回調(diào)傳遞給了nextTick函數(shù)。
  3. waiting這個標記位代表我們是否已經(jīng)向nextTick函數(shù)傳遞了更新任務(wù)恶座,nextTick會在當前task結(jié)束后再去處理傳入的回掉,只需要傳遞一次沥阳,更新完畢再重置這個標志位跨琳。

next-tick


let callbacks = [];
let pending = false;
let timerFunc;

/**----- nextTick -----*/
function nextTick (cb) {
    // 把傳進來的回調(diào)函數(shù)放到callbacks隊列里
    callbacks.push(cb);

    // pending代表一個等待狀態(tài) 等這個tick執(zhí)行
    if (!pending) {
        pending = true
        timerFunc()
    }
    
    // 如果沒傳遞回調(diào) 提供一個Promise化的調(diào)用
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
    }
}

/**----- timerFunc ----*/

// 1、優(yōu)先考慮Promise實現(xiàn)
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]')) {
// 2桐罕、降級到MutationObserver實現(xiàn)
  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)) {
// 3脉让、降級到setImmediate實現(xiàn)
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
// 4、如果以上都不支持就用setTimeout來兜底了
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

function flushCallbacks () {
  // 將callbacks中的cb依次執(zhí)行
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
  1. 傳進來的回調(diào)函數(shù)會被保存到callbacks隊列里面功炮,這里使用callbacks 而沒有在nextTick中直接執(zhí)行回調(diào)函數(shù)溅潜,是因為這樣可以保證在同一個tick 內(nèi)多次執(zhí)行nextTick,在一個tick里面完成渲染薪伏,不會開啟多個異步任務(wù)滚澜。

    // 舉個栗子??
    // 假如我們直接在nexttick里面直接執(zhí)行回調(diào)
    
    function nextTick (cb) {
        setTimeout(cb)
    }
    nextTick(cb1)
    nextTick(cb2)
    
    這種情況下就會開啟兩個異步任務(wù),也就是兩次事件循環(huán)嫁怀,造成了頁面不必要的渲染
    
  2. timerFunc是實現(xiàn)的核心设捐,它會優(yōu)先使用Promise等microtask借浊,保證在同一個事件循環(huán)里面執(zhí)行,這樣頁面只需要渲染一次萝招。實在不行的話用setTimeout來兜底蚂斤,雖然會造成二次渲染,但這也是最差的情況槐沼。vue在這里用了降級處理的策略曙蒸。

$nextTick

最后再把nexttick函數(shù)掛到Vue原型上就OK了

Vue.prototype.$nextTick = function (fn) {
    return nextTick(fn, this)
}

小結(jié)

vue異步更新,本質(zhì)上是js事件機制的一種運用岗钩,優(yōu)先考慮了具有高優(yōu)先級的microtask纽窟,為了兼容,又做了降級策略凹嘲。

現(xiàn)在再回頭看開頭的那兩個問題

  1. for循環(huán)更新count數(shù)值师倔,dom會被更新100次嗎?

    不會周蹭,因為queueWatcher函數(shù)做了過濾趋艘,相同的watcher對象不會被重復(fù)添加。

  2. nextTick是如何做到監(jiān)聽dom更新完畢的凶朗?

    vue用異步隊列的方式來控制DOM更新和nextTick回調(diào)先后執(zhí)行瓷胧,保證了能在dom更新后在執(zhí)行回調(diào)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棚愤,一起剝皮案震驚了整個濱河市搓萧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宛畦,老刑警劉巖瘸洛,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異次和,居然都是意外死亡反肋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門踏施,熙熙樓的掌柜王于貴愁眉苦臉地迎上來石蔗,“玉大人,你說我怎么就攤上這事畅形⊙啵” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵日熬,是天一觀的道長棍厌。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么定铜? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任阳液,我火速辦了婚禮,結(jié)果婚禮上揣炕,老公的妹妹穿的比我還像新娘帘皿。我一直安慰自己,他們只是感情好畸陡,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布鹰溜。 她就那樣靜靜地躺著,像睡著了一般丁恭。 火紅的嫁衣襯著肌膚如雪曹动。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天牲览,我揣著相機與錄音墓陈,去河邊找鬼。 笑死第献,一個胖子當著我的面吹牛贡必,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庸毫,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼仔拟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了飒赃?” 一聲冷哼從身側(cè)響起利花,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎载佳,沒想到半個月后炒事,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蔫慧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年羡洛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藕漱。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖崭闲,靈堂內(nèi)的尸體忽然破棺而出肋联,到底是詐尸還是另有隱情,我是刑警寧澤刁俭,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布橄仍,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏侮繁。R本人自食惡果不足惜虑粥,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宪哩。 院中可真熱鬧娩贷,春花似錦、人聲如沸锁孟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽品抽。三九已至储笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圆恤,已是汗流浹背突倍。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盆昙,地道東北人羽历。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像弱左,于是被迫代替她去往敵國和親窄陡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359