沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 课程导读-必看
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
    • 两道代码题
    • Vue运行机制
    • 渲染器核心功能
    • 事件绑定与更新
    • computed面试题
    • watch面试题
    • 图解双端diff
    • 图解快速diff
    • 最长递增子序列
    • 模板编译器
    • 模板编译提升
    • 组件name作用
    • Vue项目性能优化
    • 路由传参方式
    • vue3笔面试题汇总
    luzhichang
    2024-09-27
    目录

    nextTick实现原理

    # nextTick实现原理

    面试题:Vue 的 nextTick 是如何实现的?

    <template>
      <div>
        <p>{{ count }}</p>
        <button @click="increment">增加计数</button>
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    
    const count = ref(0)
    
    const increment = () => {
      for (let i = 1; i <= 1000; i++) {
        count.value = i
      }
    }
    </script>
    

    思考🤔:点击按钮后,页面会渲染几次?

    答案:只会渲染一次,同步代码中多次对响应式数据做了修改,多次修改会被合并为一次,之后根据最终的修改结果异步的去更新 DOM.

    思考🤔:倘若不合并,并且同步的去修改DOM,会有什么样的问题?

    答案:如果不进行合并,并且数据一变就同步更新DOM,会导致频繁的重绘和重排,这非常耗费性能。

    思考🤔:异步更新会带来问题

    答案:无法及时获取到更新后的DOM值

    原因:因为获取DOM数据是同步代码,DOM的更新是异步的,同步代码会先于异步代码执行。

    解决方案:将获取DOM数据的同步任务包装成一个微任务,浏览器在完成一次渲染后,就会立即执行微任务。

    当前我们自己的解决方案:

    const increment = () => {
      count.value++
    
      Promise.resolve().then(() => {
        console.log('最新的数据:', count.value)
        console.log('通过DOM拿textContent数据:', counterRef.value.textContent)
        console.log('通过DOM拿textContent数据:', document.getElementById('counter').textContent)
        console.log('通过DOM拿innerHTML数据:', counterRef.value.innerHTML)
        console.log('通过DOM拿innerHTML数据:', document.getElementById('counter').innerHTML)
      })
    }
    

    nextTick 帮我们做的就是上面的事情,将一个任务包装成一个微任务。

    const increment = () => {
      count.value++
    
      nextTick(() => {
        console.log('最新的数据:', count.value)
        console.log('通过DOM拿textContent数据:', counterRef.value.textContent)
        console.log('通过DOM拿textContent数据:', document.getElementById('counter').textContent)
        console.log('通过DOM拿innerHTML数据:', counterRef.value.innerHTML)
        console.log('通过DOM拿innerHTML数据:', document.getElementById('counter').innerHTML)
      })
    }
    

    nextTick 返回的是一个 Promise

    const increment = async () => {
      count.value++
    
      await nextTick()
      console.log('最新的数据:', count.value)
      console.log('通过DOM拿textContent数据:', counterRef.value.textContent)
      console.log('通过DOM拿textContent数据:', document.getElementById('counter').textContent)
      console.log('通过DOM拿innerHTML数据:', counterRef.value.innerHTML)
      console.log('通过DOM拿innerHTML数据:', document.getElementById('counter').innerHTML)
    }
    

    $nextTick,首先这是一个方法,是 Vue 组件实例的方法,用于 OptionsAPI 风格的。

    export default {
      data() {
        return {
          count: 1,
          counterRef: null
        }
      },
      methods: {
        increment() {
          this.count++
          this.$nextTick(() => {
            // 在下一个 DOM 更新循环后执行的回调函数
            console.log('最新数据为:', this.count)
            console.log('拿到的DOM:', document.getElementById('counter'))
            console.log('拿到的DOM:', this.$refs.counterRef)
            console.log('通过DOM拿数据:', document.getElementById('counter').textContent)
            console.log('通过DOM拿数据:', document.getElementById('counter').innerHTML)
            console.log('通过DOM拿数据:', this.$refs.counterRef.textContent)
            console.log('通过DOM拿数据:', this.$refs.counterRef.innerHTML)
          })
        }
      }
    }
    

    nextTick源码 (opens new window)

    // 创建一个已经解析的 Promise 对象,这个 Promise 会立即被解决,
    // 用于创建一个微任务(microtask)。
    const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
    
    // 一个全局变量,用于跟踪当前的刷新 Promise。
    // 初始状态为 null,表示当前没有刷新任务。
    let currentFlushPromise: Promise<void> | null = null
    
    // queueFlush 函数负责将刷新任务(flushJobs)放入微任务队列。
    // 这是 Vue 的异步更新机制的核心部分,用于优化性能。
    function queueFlush() {
      // 检查是否已经在刷新(isFlushing)或者刷新任务是否已被挂起(isFlushPending)。
      if (!isFlushing && !isFlushPending) {
        // 设置 isFlushPending 为 true,表示刷新任务已被挂起,正在等待执行。
        isFlushPending = true
        // 将 currentFlushPromise 设置为 resolvedPromise.then(flushJobs)
        // 这将创建一个微任务,当 resolvedPromise 被解决时,执行 flushJobs 函数。
        currentFlushPromise = resolvedPromise.then(flushJobs)
      }
    }
    
    // nextTick 函数用于在下一个 DOM 更新循环之后执行一个回调函数。
    // 它返回一个 Promise,这个 Promise 会在 DOM 更新完成后解决。
    export function nextTick<T = void, R = void>(
      this: T,
      fn?: (this: T) => R,  // 可选的回调函数,在 DOM 更新之后执行
    ): Promise<Awaited<R>> {
      // 如果 currentFlushPromise 不为 null,使用它;否则使用 resolvedPromise。
      // 这样可以确保在 DOM 更新之后再执行回调。
      const p = currentFlushPromise || resolvedPromise
      
      // 如果传入了回调函数 fn,返回一个新的 Promise,在 p 解决之后执行 fn。
      // 使用 this 绑定来确保回调函数的上下文正确。
      return fn ? p.then(this ? fn.bind(this) : fn) : p
      // 如果没有传入回调函数 fn,直接返回 Promise p,这样外部代码可以使用 await 等待 DOM 更新完成。
    }
    

    面试题:Vue 的 nextTick 是如何实现的?

    参考答案:

    nextTick 的本质将回调函数包装为一个微任务放入到微任务队列,这样浏览器在完成渲染任务后会优先执行微任务。

    nextTick 在 Vue2 和 Vue3 里的实现有一些不同:

    1. Vue2 为了兼容旧浏览器,会根据不同的环境选择不同包装策略:
    • 优先使用 Promise,因为它是现代浏览器中最有效的微任务实现。

    • 如果不支持 Promise,则使用 MutationObserver,这是另一种微任务机制。

    • 在 IE 环境下,使用 setImmediate,这是一种表现接近微任务的宏任务。

    • 最后是 setTimeout(fn, 0) 作为兜底方案,这是一个宏任务,但会在下一个事件循环中尽快执行。

    1. Vue3 则是只考虑现代浏览器环境,直接使用 Promise 来实现微任务的包装,这样做的好处在于代码更加简洁,性能更高,因为不需要处理多种环境的兼容性问题。

    整体来讲,Vue3 的 nextTick 实现更加简洁和高效,是基于现代浏览器环境的优化版本,而 Vue2 则为了兼容性考虑,实现层面存在更多的兼容性代码。

    Vue3响应式变化
    两道代码题

    ← Vue3响应式变化 两道代码题→

    Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式