亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue異步更新機(jī)制及$nextTick原理的深入講解

 更新時(shí)間:2022年04月25日 12:40:03   作者:南玖  
最近在學(xué)習(xí)一些底層方面的知識,所以想做個(gè)系列嘗試去聊聊這些比較復(fù)雜又很重要的知識點(diǎn),下面這篇文章主要給大家介紹了關(guān)于Vue異步更新機(jī)制及$nextTick原理的相關(guān)資料,需要的朋友可以參考下

前言

相信很多人會好奇Vue內(nèi)部的更新機(jī)制,或者平時(shí)工作中遇到的一些奇怪的問題需要使用$nextTick來解決,今天我們就來聊一聊Vue中的異步更新機(jī)制以及$nextTick原理

Vue的異步更新

可能你還沒有注意到,Vue異步執(zhí)行DOM更新。只要觀察到數(shù)據(jù)變化,Vue將開啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個(gè)watcher被多次觸發(fā),只會被推入到隊(duì)列中一次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對于避免不必要的計(jì)算和DOM操作上非常重要。然后,在下一個(gè)的事件循環(huán)“tick”中,Vue刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作。

DOM更新是異步的

當(dāng)我們在更新數(shù)據(jù)后立馬去獲取DOM中的內(nèi)容是會發(fā)現(xiàn)獲取的依然還是舊的內(nèi)容。

<template>
  <div class="next_tick">
      <div ref="title" class="title">{{name}}</div>
  </div>
</template>
<script>
export default {
    data() {
        return {
            name: '前端南玖'
        }
    },
    mounted() {
        this.name = 'front end'
        console.log('sync',this.$refs.title.innerText)
        this.$nextTick(() => {
            console.log('nextTick',this.$refs.title.innerText)
        })
    }
}
</script>

從圖中我們可以發(fā)現(xiàn)數(shù)據(jù)改變后同步獲取dom元素中的內(nèi)容是老的數(shù)據(jù),而在nextTick里面獲取的是更新后的數(shù)據(jù),這是為什么呢?

其實(shí)這里你用微任務(wù)或宏任務(wù)去獲取dom元素中的內(nèi)容也是更新后的數(shù)據(jù),我們可以來試試:

mounted() {
  this.name = 'front end'
  console.log('sync',this.$refs.title.innerText)
  Promise.resolve().then(() => {
    console.log('微任務(wù)',this.$refs.title.innerText)
  })
  setTimeout(() => {
    console.log('宏任務(wù)',this.$refs.title.innerText)
  }, 0)
  this.$nextTick(() => {
    console.log('nextTick',this.$refs.title.innerText)
  })
}

是不是覺得有點(diǎn)不可思議,其實(shí)沒什么奇怪的,在vue源碼中它的實(shí)現(xiàn)原理就是利用的微任務(wù)與宏任務(wù),慢慢往下看,后面會一一解釋。

DOM更新還是批量的

沒錯(cuò),vue中的DOM更新還是批量處理的,這樣做的好處無疑就是能夠最大程度的優(yōu)化性能。OK這里也有看點(diǎn),別著急

vue同時(shí)更新了多個(gè)數(shù)據(jù),你覺得dom是更新多次還是更新一次?我們來試試

<template>
  <div class="next_tick">
      <div ref="title" class="title">{{name}}</div>
      <div class="verse">{{verse}}</div>
  </div>
</template>

<script>
export default {
    name: 'nextTick',
    data() {
        return {
            name: '前端南玖',
            verse: '如若東山能再起,大鵬展翅上九霄',
            count:0
        }
    },
    mounted() {
        this.name = 'front end'
        this.verse = '世間萬物都是空,功名利祿似如風(fēng)'
        // console.log('sync',this.$refs.title.innerText)
        // Promise.resolve().then(() => {
        //     console.log('微任務(wù)',this.$refs.title.innerText)
        // })
        // setTimeout(() => {
        //     console.log('宏任務(wù)',this.$refs.title.innerText)
        // }, 0)
        // this.$nextTick(() => {
        //     console.log('nextTick',this.$refs.title.innerText)
        // })
    },
    updated() {
        this.count++
        console.log('update:',this.count)
    }
?
}
</script>
<style lang="less">
.verse{
    font-size: (20/@rem);
}
</style>

我們可以看到updated鉤子只執(zhí)行了一次,說明我們同時(shí)更新了多個(gè)數(shù)據(jù),DOM只會更新一次

再來看另一種情況,同步與異步混合,DOM會更新幾次?

mounted() {
  this.name = 'front end'
  this.verse = '世間萬物都是空,功名利祿似如風(fēng)'
  Promise.resolve().then(() => {
    this.name = 'study ...'
  })
  setTimeout(() => {
    this.verse = '半身風(fēng)雨半身寒,一杯濁酒敬流年'
  })
  // console.log('sync',this.$refs.title.innerText)
  // Promise.resolve().then(() => {
  //     console.log('微任務(wù)',this.$refs.title.innerText)
  // })
  // setTimeout(() => {
  //     console.log('宏任務(wù)',this.$refs.title.innerText)
  // }, 0)
  // this.$nextTick(() => {
  //     console.log('nextTick',this.$refs.title.innerText)
  // })
},
  updated() {
    this.count++
    console.log('update:',this.count)
  }

從圖中我們會發(fā)現(xiàn),DOM會渲染三次,分別是同步的一次(2個(gè)同步一起更新),微任務(wù)的一次,宏任務(wù)的一次。并且在用setTimeout更新數(shù)據(jù)時(shí)會明顯看見頁面數(shù)據(jù)變化的過程。(這句話是重點(diǎn),記好小本本)這也就是為什么nextTick源碼中setTimeout做最后兜底用的,優(yōu)先使用微任務(wù)。

事件循環(huán)

沒錯(cuò),這里跟事件循環(huán)還有很大的關(guān)系,這里稍微提一下,更詳細(xì)可以看探索JavaScript執(zhí)行機(jī)制

由于JavaScript是單線程的,這就決定了它的任務(wù)不可能只有同步任務(wù),那些耗時(shí)很長的任務(wù)如果也按同步任務(wù)執(zhí)行的話將會導(dǎo)致頁面阻塞,所以JavaScript任務(wù)一般分為兩類:同步任務(wù)與異步任務(wù),而異步任務(wù)又分為宏任務(wù)與微任務(wù)。

宏任務(wù): script(整體代碼)、setTimeout、setInterval、setImmediate、I/O、UI rendering

微任務(wù): promise.then、MutationObserver

執(zhí)行過程

  • 同步任務(wù)直接放入到主線程執(zhí)行,異步任務(wù)(點(diǎn)擊事件,定時(shí)器,ajax等)掛在后臺執(zhí)行,等待I/O事件完成或行為事件被觸發(fā)。
  • 系統(tǒng)后臺執(zhí)行異步任務(wù),如果某個(gè)異步任務(wù)事件(或者行為事件被觸發(fā)),則將該任務(wù)添加到任務(wù)隊(duì)列,并且每個(gè)任務(wù)會對應(yīng)一個(gè)回調(diào)函數(shù)進(jìn)行處理。
  • 這里異步任務(wù)分為宏任務(wù)與微任務(wù),宏任務(wù)進(jìn)入到宏任務(wù)隊(duì)列,微任務(wù)進(jìn)入到微任務(wù)隊(duì)列。
  • 執(zhí)行任務(wù)隊(duì)列中的任務(wù)具體是在執(zhí)行棧中完成的,當(dāng)主線程中的任務(wù)全部執(zhí)行完畢后,去讀取微任務(wù)隊(duì)列,如果有微任務(wù)就會全部執(zhí)行,然后再去讀取宏任務(wù)隊(duì)列
  • 上述過程會不斷的重復(fù)進(jìn)行,也就是我們常說的 「事件循環(huán)(Event-Loop)」。

總的來說,在事件循環(huán)中,微任務(wù)會先于宏任務(wù)執(zhí)行。而在微任務(wù)執(zhí)行完后會進(jìn)入瀏覽器更新渲染階段,所以在更新渲染前使用微任務(wù)會比宏任務(wù)快一些,一次循環(huán)就是一次tick 。

在一次event loop中,microtask在這一次循環(huán)中是一直取一直取,直到清空microtask隊(duì)列,而macrotask則是一次循環(huán)取一次。

如果執(zhí)行事件循環(huán)的過程中又加入了異步任務(wù),如果是macrotask,則放到macrotask末尾,等待下一輪循環(huán)再執(zhí)行。如果是microtask,則放到本次event loop中的microtask任務(wù)末尾繼續(xù)執(zhí)行。直到microtask隊(duì)列清空。

源碼深入

異步更新隊(duì)列

在Vue中DOM更新一定是由于數(shù)據(jù)變化引起的,所以我們可以快速找到更新DOM的入口,也就是set時(shí)通過dep.notify通知watcher更新的時(shí)候

// watcher.js
// 當(dāng)依賴發(fā)生變化時(shí),觸發(fā)更新
update() {
  if(this.lazy) {
    // 懶執(zhí)行會走這里, 比如computed
    this.dirty = true
  }else if(this.sync) {
    // 同步執(zhí)行會走這里,比如this.$watch() 或watch選項(xiàng),傳遞一個(gè)sync配置{sync: true}
    this.run()
  }else {
    // 將當(dāng)前watcher放入watcher隊(duì)列, 一般都是走這里
    queueWatcher(this)
  }
}

從這里我們可以發(fā)現(xiàn)vue默認(rèn)就是走的異步更新機(jī)制,它會實(shí)現(xiàn)一個(gè)隊(duì)列進(jìn)行緩存當(dāng)前需要更新的watcher

// scheduler.js
/*將一個(gè)觀察者對象push進(jìn)觀察者隊(duì)列,在隊(duì)列中已經(jīng)存在相同的id則該觀察者對象將被跳過,除非它是在隊(duì)列被刷新時(shí)推送*/
export function queueWatcher (watcher: Watcher) {
  /*獲取watcher的id*/
  const id = watcher.id
  /*檢驗(yàn)id是否存在,已經(jīng)存在則直接跳過,不存在則標(biāo)記在has中,用于下次檢驗(yàn)*/
  if (has[id] == null) {
    has[id] = true
    // 如果flushing為false, 表示當(dāng)前watcher隊(duì)列沒有在被刷新,則watcher直接進(jìn)入隊(duì)列
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 如果watcher隊(duì)列已經(jīng)在被刷新了,這時(shí)候想要插入新的watcher就需要特殊處理
      // 保證新入隊(duì)的watcher刷新仍然是有序的
      let i = queue.length - 1
      while (i >= 0 && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(Math.max(i, index) + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      // wating為false,表示當(dāng)前瀏覽器的異步任務(wù)隊(duì)列中沒有flushSchedulerQueue函數(shù)
      waiting = true
      // 這就是我們常見的this.$nextTick
      nextTick(flushSchedulerQueue)
    }
  }
}

ok,從這里我們就能發(fā)現(xiàn)vue并不是跟隨數(shù)據(jù)變化立即更新視圖的,它而是維護(hù)了一個(gè)watcher隊(duì)列,并且id重復(fù)的watcher只會推進(jìn)隊(duì)列一次,因?yàn)槲覀冴P(guān)心的只是最終的數(shù)據(jù),而不是它更新多少次。等到下一個(gè)tick時(shí),這些watcher才會從隊(duì)列中取出,更新視圖。

nextTick

nextTick的目的就是產(chǎn)生一個(gè)回調(diào)函數(shù)加入task或者microtask中,當(dāng)前棧執(zhí)行完以后(可能中間還有別的排在前面的函數(shù))調(diào)用該回調(diào)函數(shù),起到了異步觸發(fā)(即下一個(gè)tick時(shí)觸發(fā))的目的。

// next-tick.js
const callbacks = []
let pending = false
?
// 批處理
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 依次執(zhí)行nextTick的方法
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
?
export function nextTick (cb, ctx) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 因?yàn)閮?nèi)部會調(diào)nextTick,用戶也會調(diào)nextTick,但異步只需要一次
  if (!pending) {
    pending = true
    timerFunc()
  }
  // 執(zhí)行完會會返回一個(gè)promise實(shí)例,這也是為什么$nextTick可以調(diào)用then方法的原因
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

兼容性處理,優(yōu)先使用promise.then 優(yōu)雅降級(兼容處理就是一個(gè)不斷嘗試的過程,誰可以就用誰。

Vue 在內(nèi)部對異步隊(duì)列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執(zhí)行環(huán)境不支持,則會采用 setTimeout(fn, 0) 代替。

// timerFunc 
// promise.then -> MutationObserver -> setImmediate -> setTimeout
// vue3 中不再做兼容性處理,直接使用的就是promise.then 任性
?
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) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks) // 可以監(jiān)聽DOM變化,監(jiān)聽完是異步更新的
  // 但這里并不是想用它做DOM監(jiān)聽,而是利用它是微任務(wù)這一特點(diǎn)
  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 {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

$nextTick

我們平常調(diào)用的$nextTick其實(shí)就是上面這個(gè)方法,只不過在源碼中renderMixin中將該方法掛在了vue的原型上方便我們使用

export function renderMixin (Vue) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

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

總結(jié)

一般更新DOM是同步的

上面說了那么多,相信大家對Vue的異步更新機(jī)制以及$nextTick原理已經(jīng)有了初步的了解。每一輪事件循環(huán)的最后會進(jìn)行一次頁面渲染,并且從上面我們知道渲染過程也是個(gè)宏任務(wù),這里可能會有個(gè)誤區(qū),那就是DOM tree的修改是同步的,只有渲染過程是異步的,也就是說我們在修改完DOM后能夠立即獲取到更新的DOM,不信我們可以來試一下:

<!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>Document</title>
</head>
<body>
    <div id="title">欲試人間煙火,怎料世道滄桑</div>
    <script>
        title.innerText = '萬卷詩書無一用,半老雄心剩疏狂'
        console.log('updated',title)
    </script>
</body>
</html>

既然更新DOM是個(gè)同步的過程,那為什么Vue卻需要借用$nextTick來處理呢?

答案很明顯,因?yàn)閂ue處于性能考慮,Vue會將用戶同步修改的多次數(shù)據(jù)緩存起來,等同步代碼執(zhí)行完,說明這一次的數(shù)據(jù)修改就結(jié)束了,然后才會去更新對應(yīng)DOM,一方面可以省去不必要的DOM操作,比如同時(shí)修改一個(gè)數(shù)據(jù)多次,只需要關(guān)心最后一次就好了,另一方面可以將DOM操作聚集,提升render性能。

看下面這個(gè)圖理解起來應(yīng)該更容易一點(diǎn)

為什么優(yōu)先使用微任務(wù)?

這個(gè)應(yīng)該不用多說吧,因?yàn)槲⑷蝿?wù)一定比宏任務(wù)優(yōu)先執(zhí)行,如果nextTick是微任務(wù),它會在當(dāng)前同步任務(wù)執(zhí)行完立即執(zhí)行所有的微任務(wù),也就是修改DOM的操作也會在當(dāng)前tick內(nèi)執(zhí)行,等本輪tick任務(wù)全部執(zhí)行完成,才是開始執(zhí)行UI rendering。如果nextTick是宏任務(wù),它會被推進(jìn)宏任務(wù)隊(duì)列,并且在本輪tick執(zhí)行完之后的某一輪執(zhí)行,注意,它并不一定是下一輪,因?yàn)槟悴淮_定宏任務(wù)隊(duì)列中它之前還有所少個(gè)宏任務(wù)在等待著。所以為了能夠盡快更新DOM,Vue中優(yōu)先采用的是微任務(wù),并且在Vue3中,它沒有了兼容判斷,直接使用的是promise.then微任務(wù),不再考慮宏任務(wù)了。

總結(jié)

到此這篇關(guān)于Vue異步更新機(jī)制及$nextTick原理的文章就介紹到這了,更多相關(guān)Vue異步更新及$nextTick原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue中v-html妙用及空白行消除方式

    vue中v-html妙用及空白行消除方式

    這篇文章主要介紹了vue中v-html妙用及空白行消除方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue translate peoject實(shí)現(xiàn)在線翻譯功能【新手必看】

    vue translate peoject實(shí)現(xiàn)在線翻譯功能【新手必看】

    這篇文章主要介紹了vue translate peoject實(shí)現(xiàn)在線翻譯功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-06-06
  • Vue使用$attrs實(shí)現(xiàn)爺爺直接向?qū)O組件傳遞數(shù)據(jù)

    Vue使用$attrs實(shí)現(xiàn)爺爺直接向?qū)O組件傳遞數(shù)據(jù)

    這篇文章主要為大家詳細(xì)介紹了Vue如何使用$attrs實(shí)現(xiàn)爺爺直接向?qū)O組件傳遞數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2024-02-02
  • vue-print-nb只打印一頁解決方法示例

    vue-print-nb只打印一頁解決方法示例

    這篇文章主要為大家介紹了vue-print-nb只打印一頁解決方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2023-11-11
  • Ant?Design?of?Vue的樹形控件Tree的使用及說明

    Ant?Design?of?Vue的樹形控件Tree的使用及說明

    這篇文章主要介紹了Ant?Design?of?Vue的樹形控件Tree的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 深入理解Vue.js源碼之事件機(jī)制

    深入理解Vue.js源碼之事件機(jī)制

    本篇文章主要介紹了Vue.js源碼之事件機(jī)制,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • vue+element+Java實(shí)現(xiàn)批量刪除功能

    vue+element+Java實(shí)現(xiàn)批量刪除功能

    這篇文章主要介紹了vue+element+Java實(shí)現(xiàn)批量刪除功能,代碼簡單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • 使用el-form-item設(shè)置標(biāo)簽長度

    使用el-form-item設(shè)置標(biāo)簽長度

    這篇文章主要介紹了使用el-form-item設(shè)置標(biāo)簽長度方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 淺談ElementUI el-select 數(shù)據(jù)過多解決辦法

    淺談ElementUI el-select 數(shù)據(jù)過多解決辦法

    下拉框的選項(xiàng)很多,上萬個(gè)選項(xiàng)甚至更多,這個(gè)時(shí)候如果全部把數(shù)據(jù)放到下拉框中渲染出來,瀏覽器會卡死,體驗(yàn)會特別不好,本文主要介紹了ElementUI el-select 數(shù)據(jù)過多解決辦法,感興趣的可以了解一下
    2021-09-09
  • 關(guān)于單文件組件.vue的使用

    關(guān)于單文件組件.vue的使用

    這篇文章主要介紹了關(guān)于單文件組件.vue的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09

最新評論