Vue源码学习 - 异步更新队列 和 nextTick原理

这篇具有很好参考价值的文章主要介绍了Vue源码学习 - 异步更新队列 和 nextTick原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在我们使用Vue的过程中,基本大部分的 watcher 更新都需要经过 异步更新 的处理。而 nextTick 则是异步更新的核心。

官方对其的定义:

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

一、Vue异步更新队列

Vue 可以做到 数据驱动视图更新,我们简单写一个案例实现下:

<template>
  <h1 style="text-align:center" @click="handleCount">{{ value }}</h1>
</template>

<script>
export default {
  data () {
    return {
      value: 0
    }
  },
  methods: {
    handleCount () {
      for (let i = 0; i <= 10; i++) {
        this.value = i
        console.log(this.value)
      }
    }
  }
}
</script>

vue异步更新dom

当我们触发这个事件,视图中的 value 肯定会发生一些变化。
这里可以思考下,Vue是如何管理这个变化的过程呢?比如上面这个案例,value 被循环了10次,那 Vue 会去渲染dom视图10次吗?显然是不会的,毕竟这个性能代价太大了。其实我们只需要 value 最后一次的赋值。

实际上 Vue 是 异步更新 视图的,也就是说等 handleCount() 事件执行完,检查发现只需要更新 value,然后再一次性更新数据和Dom,避免无效更新。

总之,Vue 的 数据更新 和 DOM更新 都是异步的,Vue 会将数据变更添加到队列中,在下一个事件循环中进行批量更新,然后异步地将变更应用于实际的 DOM 元素,以保持视图与数据的同步。

Vue官方文档也印证了我们的想法,如下:

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

详细可见:Vue官方文档 - 异步更新队列

二、nextTick 用法

看例子,比如当 DOM 内容改变后,我们需要获取最新的元素高度。

<template>
  <div>{{ name }}</div>
</template>

<script>
export default {
  data () {
    return {
      name: ''
    }
  },
  methods: {},
  mounted () {
    console.log(this.$el.clientHeight)
    this.name = '铁锤妹妹'
    console.log(this.name, 'name')
    console.log(this.$el.clientHeight)
    this.$nextTick(() => {
      console.log(this.$el.clientHeight)
    })
  }
}
</script>

Vue源码学习 - 异步更新队列 和 nextTick原理,Vue,JavaScript,vue.js,javascript,前端

从打印结果可以看出,name数据虽然更新了,但是前两次元素高度都是0,只有在 nextTick 中才能拿到更新后的 Dom 值,具体是什么原因呢?下面就分析下它的原理吧。

这个实例也可参考学习:watch监听和$nextTick结合使用处理数据渲染完成后的操作方法

三、原理分析

在执行 this.name = '铁锤妹妹' 的时候,就会触发 Watcher 更新,watcher 会把自己放入一个队列。

// src/core/observer/watcher.ts
update () {
    if (this.lazy) {
        // 如果是计算属性
        this.dirty = true
    } else if (this.sync) {
        // 如果要同步更新
        this.run()
    } else {
        // 将 Watcher 对象添加到调度器队列中,以便在适当的时机执行其更新操作。
        queueWatcher(this)
    }
}

用队列的原因是比如多个数据变更,直接更新视图多次的话,性能就会降低,所以对视图更新做一个异步更新的队列,避免不必要的计算和 DOM 操作。在下一轮事件循环的时候,刷新队列并执行已去重的工作(nextTick的回调函数),组件重新渲染,更新视图。

然后调用 nextTick() ,响应式派发更新的源码如下:

// src/core/observer/scheduler.ts

export function queueWatcher(watcher: Watcher) {
    // ...
    
   // 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用
    nextTick(flushSchedulerQueue)
}

function flushSchedulerQueue () {
    queue.sort(sortCompareFn)
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        watcher.run()
        // ...省略细节代码
    }
}

这里参数 flushSchedulerQueue 方法就会被放入事件循环,主线程任务执行完之后就会执行这个函数,对 watcher 队列排序遍历、执行 watcher 对应的 run() 方法,然后render,更新视图。

也就是说 this.name = '铁锤妹妹' 的时候,任务队列简单理解成这样 [flushSchedulerQueue]

下一行 console.log(this.name, 'name') 检验下 name 数据是否更新。

然后下一行 console.log(this.$el.clientHeight) ,由于更新视图任务 flushSchedulerQueue 在任务队列中还没有执行,所以无法拿到更新后的视图。

然后执行 this.$nextTick(fn) 时候,添加一个异步任务,这时任务队列简单理解成这样 [flushSchedulerQueue, fn]

然后 同步任务 都执行完毕,接着按顺序执行任务队列中的 异步任务。第一个任务执行就会更新视图,后面自然能得到更新后的视图了。

四、nextTick 源码解析

1)环境判断

主要判断用哪个宏任务或者微任务,因为宏任务耗费时间大于微任务,所以优先使用 微任务,判断顺序如下:
Promise =》 MutationObserver =》 setImmediate =》 setTimeout

// src/core/util/next-tick.ts

export let isUsingMicroTask = false  // 是否启用微任务开关

const callbacks: Array<Function> = [] //回调队列
let pending = false  // 异步控制开关,标记是否正在执行回调函数

// 该方法负责执行队列中的全部回调
function flushCallbacks() {
  // 重置异步开关
  pending = false
  // 防止nextTick里有nextTick出现的问题
  // 所以执行之前先备份并清空回调队列
  const copies = callbacks.slice(0)
  callbacks.length = 0
   // 执行任务队列
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

// timerFunc就是nextTick传进来的回调等... 细节不展开
let timerFunc
// 判断当前环境是否支持原生 Promise
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]')
) {
  // 当原生 Promise 不可用时,timerFunc 使用原生 MutationObserver
  // MutationObserver不要在意它的功能,其实就是个可以达到微任务效果的备胎
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
  // 使用 MutationObserver
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 使用setImmediate,虽然也是宏任务,但是比setTimeout更好
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 最后的倔强,timerFunc 使用 setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

然后进入核心的 nextTick。

2)nextTick()

这里代码不多,主要逻辑就是:

  • 把传入的回调函数放进回调队列 callbacks
  • 执行保存的异步任务 timeFunc,就会遍历 callbacks 执行相应的回调函数了。
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
  let _resolve
  // 把回调函数放入回调队列
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e: any) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    // 如果异步开关是开的,就关上,表示正在执行回调函数,然后执行回调函数
    pending = true
    timerFunc()
  }
  // 如果没有提供回调,并且支持 Promise,就返回一个 Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

可以看到最后有返回一个 Promise 是可以让我们在不传参的时候用的,如下

this.$nextTick().then(()=>{ ... })

五、补充

  • 在 vue 生命周期中,如果在 created() 钩子进行 DOM 操作,也一定要放在 nextTick() 的回调函数中。
  • 因为在 created() 钩子函数中,页面的 DOM未渲染,这时候也没办法操作 DOM,所以,此时如果想要操作 DOM,必须将操作的代码放在 nextTick() 的回调函数中

本文到此也就结束了,希望对大家有所帮助。文章来源地址https://www.toymoban.com/news/detail-610692.html

到了这里,关于Vue源码学习 - 异步更新队列 和 nextTick原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 揭秘Vue 2中的$nextTick:等待DOM更新的神奇时刻!

    在 Vue 2 中, $nextTick 是一个异步方法,用于在下次 DOM 更新循环结束后执行回调函数。 它的原理可以解析如下: Vue 2 维护了一个队列,用于存储需要延迟执行的回调函数。 下面是一个简单的代码案例,演示了 Vue 2 中的 $nextTick 方法和队列机制: HTML: JavaScript: 在上述代码中,

    2024年02月12日
    浏览(27)
  • Vue中$nextTick主要作用、原理及使用方法

    查看本专栏目录 关于作者 还是大剑师兰特 :曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,echarts等技术开发,欢迎加底部微信,一起交流。 热门推荐 内容链接

    2024年02月21日
    浏览(31)
  • Vue学习笔记5-- nextTick | Vue封装的过渡与动画

    语法: this.$nextTick(回调函数) 作用:在下一次DOM更新结束后执行其指定的回调。 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数 在一个函数中,一般要函数执行完之后才会更新DOM,但有些涉及到DOM的操作比如获取焦点 this.

    2024年01月17日
    浏览(29)
  • Vue源码学习 - 数据响应式原理

    Vue.Js的核心包括一套 “响应式系统”。“响应式”,是指当数据改变后,Vue会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。 vue2.X的数据响应式是利用 Object.defineProperty() 实现的,通过定义对象属性 getter/setter 拦截对属性的获取和

    2024年02月15日
    浏览(25)
  • 深入理解Async/Await:从原理到实践的JavaScript异步编程指南

    理解 async/await 的原理和使用方法是理解现代JavaScript异步编程的关键。这里我会提供一个详细的实例,涵盖原理、流程、使用方法以及一些注意事项。代码注释会尽量详尽,确保你理解每个步骤。 实例:使用async/await进行异步操作 详细解释和注释: 异步函数定义: async func

    2024年02月05日
    浏览(49)
  • 【Spring云原生系列】Spring RabbitMQ:异步处理机制的基础--消息队列 原理讲解+使用教程

    🎉🎉 欢迎光临,终于等到你啦 🎉🎉 🏅我是 苏泽 ,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.

    2024年03月15日
    浏览(42)
  • FedAT:异步更新联邦学习方法

    文章链接:FedAT: A Communication-Efficient Federated Learning Method with Asynchronous Tiers under Non-IID Data 发表会议: SC’21 (International Conference for High Performance Computing, Networking, Storage, and Analysis) 高性能计算,体系结构领域顶级会议,CCF-A 联邦学习(Federated Learning, FL )涉及在大规模分布式设备上

    2024年02月06日
    浏览(30)
  • vue详细用法,ajax异步请求,案例分析 Element-ui组件使用,表单提交更新,下载,超详细

    标签及属性的使用 1.1 template 标签 1.2 form表单 1.2.1 ref属性 1.2.2 :model属性 1.2.3 :rules属性 1.2.4 label-width属性 1.2.5 el-row标签 1.2.5 el-col 标签 :span=\\\"12\\\"属性 1.2.5 el-form-item 标签 1.2.6 label 属性 1.2.7 prop属性 required 1.2.9 v-model属性 1.2.8 placeholder属性 效果图: 1.3 Select 选择器 当选项过多时,

    2023年04月12日
    浏览(51)
  • JavaScript中的事件循环机制,包括事件循环的原理、宏任务和微任务、事件队列和调用栈、以及如何优化事件循环

    JavaScript中的事件循环机制是JavaScript运行引擎的核心之一,它决定了代码的执行方式和效率。本文将从几个方面介绍JavaScript中的事件循环机制,包括事件循环的原理、宏任务和微任务、事件队列和调用栈、以及如何优化事件循环。 一、事件循环的原理 事件循环是JavaScript中实

    2024年02月05日
    浏览(34)
  • 【学习日记2023.6.19】 之 RabbitMQ服务异步通信_消息可靠性_死信交换机_惰性队列_MQ集群

    消息队列在使用过程中,面临着很多实际问题需要思考: 消息从发送,到消费者接收,会经历多个过程: 其中的每一步都可能导致消息丢失,常见的丢失原因包括: 发送时丢失: 生产者发送的消息未送达exchange 消息到达exchange后未到达queue MQ宕机,queue将消息丢失 consumer接收

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包