我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Vue异步更新机制及$nextTick原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Vue异步更新机制及$nextTick原理是什么

本文小编为大家详细介绍“Vue异步更新机制及$nextTick原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue异步更新机制及$nextTick原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    Vue的异步更新

    可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作。

    DOM更新是异步的

    当我们在更新数据后立马去获取DOM中的内容是会发现获取的依然还是旧的内容。

    <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>

    Vue异步更新机制及$nextTick原理是什么

    从图中我们可以发现数据改变后同步获取dom元素中的内容是老的数据,而在nextTick里面获取的是更新后的数据,这是为什么呢?

    其实这里你用微任务或宏任务去获取dom元素中的内容也是更新后的数据,我们可以来试试:

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

    Vue异步更新机制及$nextTick原理是什么

    是不是觉得有点不可思议,其实没什么奇怪的,在vue源码中它的实现原理就是利用的微任务与宏任务,慢慢往下看,后面会一一解释。

    DOM更新还是批量的

    没错,vue中的DOM更新还是批量处理的,这样做的好处无疑就是能够最大程度的优化性能。OK这里也有看点,别着急

    vue同时更新了多个数据,你觉得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 = '世间万物都是空,功名利禄似如风'        // console.log('sync',this.$refs.title.innerText)        // Promise.resolve().then(() => {        //     console.log('微任务',this.$refs.title.innerText)        // })        // setTimeout(() => {        //     console.log('宏任务',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>

    Vue异步更新机制及$nextTick原理是什么

    我们可以看到updated钩子只执行了一次,说明我们同时更新了多个数据,DOM只会更新一次

    再来看另一种情况,同步与异步混合,DOM会更新几次?

    mounted() {  this.name = 'front end'  this.verse = '世间万物都是空,功名利禄似如风'  Promise.resolve().then(() => {    this.name = 'study ...'  })  setTimeout(() => {    this.verse = '半身风雨半身寒,一杯浊酒敬流年'  })  // console.log('sync',this.$refs.title.innerText)  // Promise.resolve().then(() => {  //     console.log('微任务',this.$refs.title.innerText)  // })  // setTimeout(() => {  //     console.log('宏任务',this.$refs.title.innerText)  // }, 0)  // this.$nextTick(() => {  //     console.log('nextTick',this.$refs.title.innerText)  // })},  updated() {    this.count++    console.log('update:',this.count)  }

    Vue异步更新机制及$nextTick原理是什么

    从图中我们会发现,DOM会渲染三次,分别是同步的一次(2个同步一起更新),微任务的一次,宏任务的一次。并且在用setTimeout更新数据时会明显看见页面数据变化的过程。(这句话是重点,记好小本本)这也就是为什么nextTick源码中setTimeout做最后兜底用的,优先使用微任务。

    事件循环

    没错,这里跟事件循环还有很大的关系,这里稍微提一下,更详细可以看探索JavaScript执行机制

    由于JavaScript是单线程的,这就决定了它的任务不可能只有同步任务,那些耗时很长的任务如果也按同步任务执行的话将会导致页面阻塞,所以JavaScript任务一般分为两类:同步任务与异步任务,而异步任务又分为宏任务与微任务。

    宏任务: script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering

    微任务: promise.then、MutationObserver

    执行过程

    • 同步任务直接放入到主线程执行,异步任务(点击事件,定时器,ajax等)挂在后台执行,等待I/O事件完成或行为事件被触发。

    • 系统后台执行异步任务,如果某个异步任务事件(或者行为事件被触发),则将该任务添加到任务队列,并且每个任务会对应一个回调函数进行处理。

    • 这里异步任务分为宏任务与微任务,宏任务进入到宏任务队列,微任务进入到微任务队列。

    • 执行任务队列中的任务具体是在执行栈中完成的,当主线程中的任务全部执行完毕后,去读取微任务队列,如果有微任务就会全部执行,然后再去读取宏任务队列

    • 上述过程会不断的重复进行,也就是我们常说的 「事件循环(Event-Loop)」

    总的来说,在事件循环中,微任务会先于宏任务执行。而在微任务执行完后会进入浏览器更新渲染阶段,所以在更新渲染前使用微任务会比宏任务快一些,一次循环就是一次tick 。

    Vue异步更新机制及$nextTick原理是什么

    在一次event loop中,microtask在这一次循环中是一直取一直取,直到清空microtask队列,而macrotask则是一次循环取一次。

    如果执行事件循环的过程中又加入了异步任务,如果是macrotask,则放到macrotask末尾,等待下一轮循环再执行。如果是microtask,则放到本次event loop中的microtask任务末尾继续执行。直到microtask队列清空。

    源码深入

    异步更新队列

    在Vue中DOM更新一定是由于数据变化引起的,所以我们可以快速找到更新DOM的入口,也就是set时通过dep.notify通知watcher更新的时候

    // watcher.js// 当依赖发生变化时,触发更新update() {  if(this.lazy) {    // 懒执行会走这里, 比如computed    this.dirty = true  }else if(this.sync) {    // 同步执行会走这里,比如this.$watch() 或watch选项,传递一个sync配置{sync: true}    this.run()  }else {    // 将当前watcher放入watcher队列, 一般都是走这里    queueWatcher(this)  }}

    从这里我们可以发现vue默认就是走的异步更新机制,它会实现一个队列进行缓存当前需要更新的watcher

    // scheduler.jsexport function queueWatcher (watcher: Watcher) {    const id = watcher.id    if (has[id] == null) {    has[id] = true    // 如果flushing为false, 表示当前watcher队列没有在被刷新,则watcher直接进入队列    if (!flushing) {      queue.push(watcher)    } else {      // 如果watcher队列已经在被刷新了,这时候想要插入新的watcher就需要特殊处理      // 保证新入队的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,表示当前浏览器的异步任务队列中没有flushSchedulerQueue函数      waiting = true      // 这就是我们常见的this.$nextTick      nextTick(flushSchedulerQueue)    }  }}

    ok,从这里我们就能发现vue并不是跟随数据变化立即更新视图的,它而是维护了一个watcher队列,并且id重复的watcher只会推进队列一次,因为我们关心的只是最终的数据,而不是它更新多少次。等到下一个tick时,这些watcher才会从队列中取出,更新视图。

    nextTick

    nextTick的目的就是产生一个回调函数加入task或者microtask中,当前栈执行完以后(可能中间还有别的排在前面的函数)调用该回调函数,起到了异步触发(即下一个tick时触发)的目的。

    // next-tick.jsconst callbacks = []let pending = false// 批处理function flushCallbacks () {  pending = false  const copies = callbacks.slice(0)  callbacks.length = 0  // 依次执行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)    }  })  // 因为内部会调nextTick,用户也会调nextTick,但异步只需要一次  if (!pending) {    pending = true    timerFunc()  }  // 执行完会会返回一个promise实例,这也是为什么$nextTick可以调用then方法的原因  if (!cb && typeof Promise !== 'undefined') {    return new Promise(resolve => {      _resolve = resolve    })  }}

    兼容性处理,优先使用promise.then 优雅降级(兼容处理就是一个不断尝试的过程,谁可以就用谁。

    Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 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) // 可以监听DOM变化,监听完是异步更新的  // 但这里并不是想用它做DOM监听,而是利用它是微任务这一特点  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

    我们平常调用的$nextTick其实就是上面这个方法,只不过在源码中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() {    //...  }  // ...}

    总结

    一般更新DOM是同步的

    上面说了那么多,相信大家对Vue的异步更新机制以及$nextTick原理已经有了初步的了解。每一轮事件循环的最后会进行一次页面渲染,并且从上面我们知道渲染过程也是个宏任务,这里可能会有个误区,那就是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>

    Vue异步更新机制及$nextTick原理是什么

    既然更新DOM是个同步的过程,那为什么Vue却需要借用$nextTick来处理呢?

    答案很明显,因为Vue处于性能考虑,Vue会将用户同步修改的多次数据缓存起来,等同步代码执行完,说明这一次的数据修改就结束了,然后才会去更新对应DOM,一方面可以省去不必要的DOM操作,比如同时修改一个数据多次,只需要关心最后一次就好了,另一方面可以将DOM操作聚集,提升render性能。

    看下面这个图理解起来应该更容易一点

    Vue异步更新机制及$nextTick原理是什么

    为什么优先使用微任务?

    这个应该不用多说吧,因为微任务一定比宏任务优先执行,如果nextTick是微任务,它会在当前同步任务执行完立即执行所有的微任务,也就是修改DOM的操作也会在当前tick内执行,等本轮tick任务全部执行完成,才是开始执行UI rendering。如果nextTick是宏任务,它会被推进宏任务队列,并且在本轮tick执行完之后的某一轮执行,注意,它并不一定是下一轮,因为你不确定宏任务队列中它之前还有所少个宏任务在等待着。所以为了能够尽快更新DOM,Vue中优先采用的是微任务,并且在Vue3中,它没有了兼容判断,直接使用的是promise.then微任务,不再考虑宏任务了。

    读到这里,这篇“Vue异步更新机制及$nextTick原理是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Vue异步更新机制及$nextTick原理是什么

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Vue异步更新机制及$nextTick原理是什么

    本文小编为大家详细介绍“Vue异步更新机制及$nextTick原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue异步更新机制及$nextTick原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧
    2023-06-30

    Vue2异步更新及nextTick原理是什么

    这篇文章主要介绍“Vue2异步更新及nextTick原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue2异步更新及nextTick原理是什么”文章能帮助大家解决问题。JavaScript
    2023-07-05

    Vue异步更新机制和nextTick原理实例分析

    这篇文章主要介绍“Vue异步更新机制和nextTick原理实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue异步更新机制和nextTick原理实例分析”文章能帮助大家解决问题。1. 异步更
    2023-06-27

    Vue异步更新DOM及$nextTick执行机制解读

    这篇文章主要介绍了Vue异步更新DOM及$nextTick执行机制解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-24

    Vue2异步更新及nextTick原理详解

    Vue2的异步更新机制是基于JavaScript的事件循环机制实现的。nextTick方法则是在DOM更新后执行回调函数。本文详细介绍了Vue2的异步更新机制和nextTick原理,对于理解Vue2的渲染机制和优化性能有很大的帮助。
    2023-05-15

    vue的异步数据更新机制与$nextTick使用方法是什么

    这篇文章主要讲解了“vue的异步数据更新机制与$nextTick使用方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue的异步数据更新机制与$nextTick使用方法是什么”吧!v
    2023-07-05

    Vue异步更新DOM及$nextTick执行机制源码分析

    本篇内容介绍了“Vue异步更新DOM及$nextTick执行机制源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Vue异步更新DOM策
    2023-07-05

    Vue3组件异步更新和nextTick运行机制是什么

    这篇文章主要讲解了“Vue3组件异步更新和nextTick运行机制是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Vue3组件异步更新和nextTick运行机制是什么”吧!组件的异步更新
    2023-07-06

    在vue中nextTick用法及nextTick的原理是什么

    这篇文章主要介绍了在vue中nextTick用法及nextTick的原理是什么,Vue.js是一个流行的前端框架,它提供了一种响应式的数据绑定机制,使得页面的数据与页面的UI组件之间能够自动同步,需要的朋友可以参考下
    2023-05-16

    java异常处理机制原理是什么

    Java异常处理机制的原理是基于异常(Exception)的概念。在Java中,异常是指在程序运行过程中发生的错误或异常情况。当代码出现异常时,可以使用异常处理机制来捕获和处理异常,以保证程序的正常运行。Java的异常处理机制基于三个关键词
    2023-09-16

    android异步消息处理机制是什么

    Android异步消息处理机制是一种在主线程以外的线程中执行任务的机制。它主要包括以下几个重要的组件:1. Handler:负责发送和处理消息,它可以与Looper关联,通过Looper从消息队列中获取消息并处理。2. Message:消息
    2023-09-13

    java中异常处理机制的原理是什么

    Java中的异常处理机制是基于异常类的继承关系和异常处理代码块的机制。当程序发生异常时,会抛出一个异常对象,该异常对象会沿着调用链向上抛出,直到被捕获并处理或者直到程序终止。Java中的异常类都是从java.lang.Throwable类派
    2023-08-31

    .NET4中异常处理的新机制是什么

    本篇内容介绍了“.NET4中异常处理的新机制是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在.NET 4.0之后,CLR将会区别出一些
    2023-06-17

    Java中同步机制的底层原理是什么

    Java中同步机制的底层原理是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。同步机制源码初探ReentrantLock是我们常用的一种可重入互斥锁,是synchroni
    2023-06-16

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录