浅析vue中$nextTick的作用与原理

 更新时间:2023年10月20日 14:05:24   作者:前端技术栈  
这篇文章主要为大家详细介绍一下Vue中$nextTick的作用于原理,这也是面试中常常考到的问题,文中的示例代码讲解详细,对我们深入了解Vue有一定的帮助,需要的小伙伴可以参考一下

一、为什么使用nextTick

因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,

而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;

这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数,

如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的。

二、nextTick 作用

nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;

使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中;

下面是一个简单示例,展示了如何使用nextTick:

new Vue({
  data() {
    return {
      message: 'Hello, Vue!'
    }
  },
  mounted() {
    this.message = 'Modified message'
    this.$nextTick(() => {
      // 在DOM更新之后执行的操作
      console.log(this.$el.textContent) // 输出:"Modified message"
    })
  }
})
 

代码解析:

第一次 console.log 的时候,获取的到的是旧值,这是因为 value 数据发生变化的时候,Vue 没有立刻去更新 DOM ,而是将修改数据的操作放在了一个异步操作队列中,如果一直修改相同数据,异步操作队列还会进行去重,等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行 DOM 的更新
第二次的 console.log 是放到 this.$nextTick 回调函数中的,此时获取到的是新值,是因为 nextTick 的回调函数是在 DOM 更新之后触发的

三、nextTick 原理

将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务;

nextTick 提供了四种异步方法 Promise.then、MutationObserver、setImmediate、setTimeout(fn,0)

源码解读

源码位置 core/util/next-tick

源码并不复杂,三个函数,60几行代码,沉下心去看!

Tips:为了便于理解我调整了源码中 nextTick、timerFunc、flushCallbacks 三个函数的书写顺序

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
 
//  上面三行与核心代码关系不大,了解即可
//  noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错
//  handleError 错误处理函数
//  isIE, isIOS, isNative 环境判断函数,
//  isNative 判断某个属性或方法是否原生支持,如果不支持或通过第三方实现支持都会返回 false
 
 
export let isUsingMicroTask = false     // 标记 nextTick 最终是否以微任务执行
 
const callbacks = []     // 存放调用 nextTick 时传入的回调函数
let pending = false     // 标记是否已经向任务队列中添加了一个任务,如果已经添加了就不能再添加了
    // 当向任务队列中添加了任务时,将 pending 置为 true,当任务被执行时将 pending 置为 false
    // 
 
 
// 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数
// 回调的 this 自动绑定到调用它的实例上
export function nextTick(cb?: Function, ctx?: Object) {
    let _resolve
    // 将传入的回调函数存放到数组中,后面会遍历执行其中的回调
    callbacks.push(() => {
        if (cb) {   // 对传入的回调进行 try catch 错误捕获
            try {
                cb.call(ctx)
            } catch (e) {    // 进行统一的错误处理
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    
    // 如果当前没有在 pending 的回调,
    // 就执行 timeFunc 函数选择当前环境优先支持的异步方法
    if (!pending) {
        pending = true
        timerFunc()
    }
    
    // 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise
    // 在返回的这个 promise.then 中 DOM 已经更新好了,
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}
 
 
// 判断当前环境优先支持的异步方法,优先选择微任务
// 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout
// setTimeout 可能产生一个 4ms 的延迟,而 setImmediate 会在主线程执行完后立刻执行
// setImmediate 在 IE10 和 node 中支持
 
// 当在同一轮事件循环中多次调用 nextTick 时 ,timerFunc 只会执行一次
 
let timerFunc   
// 判断当前环境是否原生支持 promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {  // 支持 promise
    const p = Promise.resolve()
    timerFunc = () => {
       // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
        // 这里的 setTimeout 是用来强制刷新微任务队列的
        // 因为在 ios 下 promise.then 后面没有宏任务的话,微任务队列不会刷新
    }
    // 标记当前 nextTick 使用的微任务
    isUsingMicroTask = true
    
    
    // 如果不支持 promise,就判断是否支持 MutationObserver
    // 不是IE环境,并且原生支持 MutationObserver,那也是一个微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
    let counter = 1
    // new 一个 MutationObserver 类
    const observer = new MutationObserver(flushCallbacks) 
    // 创建一个文本节点
    const textNode = document.createTextNode(String(counter))   
    // 监听这个文本节点,当数据发生变化就执行 flushCallbacks 
    observer.observe(textNode, { characterData: true })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)  // 数据更新
    }
    isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务
    
    
    // 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => { setImmediate(flushCallbacks)  }
} else {
 
    // 以上三种都不支持就选择 setTimeout
    timerFunc = () => { setTimeout(flushCallbacks, 0) }
}
 
 
// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
// 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {
    pending = false    
    const copies = callbacks.slice(0)    // 拷贝一份 callbacks
    callbacks.length = 0    // 清空 callbacks
    for (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调
        copies[i]()
    }
}
 
// 为什么要拷贝一份 callbacks
 
// 用 callbacks.slice(0) 将 callbacks 拷贝出来一份,
// 是因为考虑到在 nextTick 回调中可能还会调用 nextTick 的情况,
// 如果在 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调,
// 而 nextTick 回调中的 nextTick 应该放在下一轮执行,
// 否则就可能出现一直循环的情况,
// 所以需要将 callbacks 复制一份出来然后清空,再遍历备份列表执行回调

到此这篇关于浅析vue中$nextTick的作用与原理的文章就介绍到这了,更多相关vue $nextTick内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue中get方法\post方法如何传递数组参数详解

    vue中get方法\post方法如何传递数组参数详解

    在前后端交互的时候,有时候需要通过get或者delete传递一个数组给后台,下面下面这篇文章主要给大家介绍了关于vue中get方法\post方法如何传递数组参数,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • vue2引入Three.js预览3D文件的实现方法

    vue2引入Three.js预览3D文件的实现方法

    three.js是一个基于WebGL的JavaScript库,它允许开发者在网页上创建和显示3D图形,这篇文章主要介绍了vue2引入Three.js预览3D文件的实现方法,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-10-10
  • 深入探索VueJS Scoped CSS 实现原理

    深入探索VueJS Scoped CSS 实现原理

    这篇文章主要介绍了深入探索VueJS Scoped CSS 实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • vue3+element Plus实现表格前端分页完整示例

    vue3+element Plus实现表格前端分页完整示例

    这篇文章主要给大家介绍了关于vue3+element Plus实现表格前端分页的相关资料,虽然很多时候后端会把分页,搜索,排序都做好,但是有些返回数据并不多的页面,或者其他原因不能后端分页的通常会前端处理,需要的朋友可以参考下
    2023-08-08
  • VUE项目中调用高德地图的全流程讲解

    VUE项目中调用高德地图的全流程讲解

    这篇文章主要介绍了VUE项目中调用高德地图的全流程讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Vue项目保存代码之后页面自动更新问题

    Vue项目保存代码之后页面自动更新问题

    这篇文章主要介绍了Vue项目保存代码之后页面自动更新问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • 解决vue接口数据赋值给data没有反应的问题

    解决vue接口数据赋值给data没有反应的问题

    今天小编就为大家分享一篇解决vue接口数据赋值给data没有反应的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • el-date-picker时间清空值为null处理方案

    el-date-picker时间清空值为null处理方案

    本文介绍关于Vue.js项目中时间选择器组件的问题,当选择后清空导致值变为null,进而引发后台接口报错,通过监听`overallForm.time`的值并设置为空数组,成功解决此问题,确保了数据正确性,同时,建议避免直接监听整个对象以优化性能,感兴趣的朋友一起看看吧
    2024-08-08
  • Vue 实现高级穿梭框 Transfer 封装过程

    Vue 实现高级穿梭框 Transfer 封装过程

    本文介绍了基于Vue2和Element-UI实现的高级穿梭框组件Transfer的设计与技术方案,组件支持多项选择,并能实时同步已选择项,包含竖版和横版设计稿,并提供了组件的使用方法和源码,此组件具备本地分页和搜索功能,适用于需要在两个列表间进行数据选择和同步的场景
    2024-09-09
  • vue组件props属性监听不到值变化问题

    vue组件props属性监听不到值变化问题

    这篇文章主要介绍了vue组件props属性监听不到值变化问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04

最新评论