【Vue3 源码解析】nextTick

news2024/10/7 20:32:23

nextTick 是 Vue 3 中用于异步执行回调函数的函数,它会将回调函数延迟到下一个微任务队列中执行。其中,Vue 更新 DOM 是异步的。下面是对 nextTick 函数的详细解释:

export function nextTick<T = void, R = void>(
  this: T,
  fn?: (this: T) => R
): Promise<Awaited<R>> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}
let currentFlushPromise: Promise<void> | null = null
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>

解释如下:

  1. 函数签名:

    • nextTick<T = void, R = void>(this: T, fn?: (this: T) => R): Promise<Awaited<R>>:这是 nextTick 函数的类型签名。它接受以下参数:
      • this:不是参数,是ts中的一个语法,给 this 定义类型。给用于绑定回调函数中的 this 上下文,可以省略。
      • fn:要异步执行的回调函数,是一个函数,可以省略。
    • 函数返回一个 PromisePromise 的泛型为 Awaited<R>,表示回调函数执行后的返回值。
  2. 实现逻辑:

    • nextTick 函数首先定义了一个 Promise 对象 p,这个 Promise 对象被初始化为 currentFlushPromiseresolvedPromise,取决于当前是否处于刷新队列(flush)的过程中。currentFlushPromise 是一个全局变量,用于表示当前正在刷新队列,如果不在刷新队列中,则使用 resolvedPromise,它是一个已经 resolved(已解决)的 Promise

    • 接着,函数检查是否传入了回调函数 fn。如果传入了 fn,则会返回一个 Promise.then(),去执行一个微任务,这个 Promise 在微任务队列中执行回调函数(如果使用 await nextTick() 也相当于执行一个微任务)。如果没有传入 fn,则直接返回 p,这意味着如果没有回调函数,会返回一个已经 resolved 的 Promise

    • 在返回 Promise 的过程中,函数会根据传入的 this 上下文(如果有的话),使用 fn.bind(this) 来绑定回调函数中的 this,确保回调函数在执行时具有正确的上下文。

  3. 总结:

    • nextTick 函数用于将回调函数异步执行,将回调函数放入下一个微任务队列中。它的核心逻辑是使用 Promise 来管理回调函数的异步执行,也就是把我们的代码放到一个 Promise 中去执行微任务,把我们的代码变成异步的。
    • 如果不传入回调函数,将返回一个已经 resolved Promise。这个函数在 Vue 3 中用于在数据更新后执行回调函数,确保数据更新后的 DOM 操作在下一个微任务中执行,以提高性能和响应速度。

Vue 更新 DOM 是异步的,然后我们看一下队列是怎么更新的。

  // job 是组件实例上的 update 方法,生成 instance.update 函数
export function queueJob(job: SchedulerJob) {
  // the dedupe search uses the startIndex argument of Array.includes()
  // by default the search index includes the current job that is being run
  // so it cannot recursively trigger itself again.
  // if the job is a watch() callback, the search will start with a +1 index to
  // allow it recursively trigger itself - it is the user's responsibility to
  // ensure it doesn't end up in an infinite loop.

  // 检查任务队列是否为空,或者当前任务是否已经在任务队列中
  if (
    !queue.length || // 如果队列为空,直接将任务添加到队列中
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
    )
  ) {
    // 如果任务的 id 为 null,直接将任务添加到队列末尾
    if (job.id == null) {
      queue.push(job)
    } else {
      // 如果任务的 id 不为 null,根据任务的 id 寻找插入位置
      // 将任务插入到合适的位置,以保持任务按照 id 的顺序执行
      queue.splice(findInsertionIndex(job.id), 0, job)
    }
    // 触发任务刷新,开始执行任务队列中的任务
    queueFlush()
  }
}
const queue: SchedulerJob[] = []

这段代码是 Vue 3 内部用于调度和执行任务的关键部分,主要涉及到任务队列的管理和触发任务执行的逻辑。

解释如下:

  1. queueJob 函数用于将一个任务 job 添加到任务队列 queue 中,并触发任务刷新(queueFlush)。

  2. 在添加任务之前,首先会进行一些条件判断:

    • 首先检查任务队列是否为空,如果为空,直接将任务添加到队列中,不需要进行后续的检查。
    • 然后,使用 queue.includes 方法检查当前任务 job 是否已经在任务队列中。这里的检查是为了避免任务的重复添加。如果任务队列中已经包含了当前任务,就不再添加。
  3. 如果当前任务 job 通过了上述检查条件,就可以将它添加到任务队列中:

    • 如果任务的 id 属性为 null,表示没有指定任务的唯一标识,直接将任务添加到队列末尾(queue.push(job))。
    • 如果任务的 id 属性不为 null,表示指定了任务的唯一标识,需要根据该标识找到合适的插入位置,以保持任务队列中的任务按照 id 的顺序执行(按照 job id 自增的顺序排列)。这时会调用 findInsertionIndex(job.id) 来找到插入位置,然后使用 splice 方法插入任务到指定位置。
  4. 最后,调用 queueFlush 函数,触发任务刷新。任务刷新会开始执行任务队列中的任务。

总结:queueJob 函数用于添加任务到任务队列中,并确保任务不会重复添加。它也负责触发任务刷新,以开始执行任务队列中的任务。这是 Vue 3 中任务调度的核心逻辑之一,用于管理异步任务的执行顺序。

以上的 idjob 都存在于 getCurrentInstance 中,因此可以打印看一下:

import { reactive, ref, nextTick, getCurrentInstance, watch } from 'vue'
const instance = getCurrentInstance()
console.log(instance);

在这里插入图片描述

返回一个对象,源码里面的 id 就是这里的 uid ,里面的 job 函数就是这里的 update 函数。也就是说:const update = (instance.update = () => effect.run()) , update.id = instance.uid。所以,源码就是获取了这两者,放入 queue 队列。

function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    // 如果没有正在刷新任务队列(isFlushing 为 false)
    // 且没有挂起的刷新请求(isFlushPending 为 false)

    // 设置 isFlushPending 为 true,表示有一个刷新请求挂起
    isFlushPending = true

    // 使用 resolvedPromise.then(flushJobs) 来延迟执行 flushJobs 函数
    // 这样可以让当前的 JavaScript 执行栈完成后再执行刷新操作
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

这段代码是 Vue 3 内部用于触发任务刷新(flushJobs)的逻辑。它确保在没有正在刷新任务队列(isFlushingfalse)且没有挂起的刷新请求(isFlushPendingfalse)时才会触发任务刷新。

解释如下:

  1. queueFlush 函数用于触发任务刷新,但它不会立即执行刷新操作,而是通过 Promise 的方式进行延迟执行。

  2. 在执行触发刷新前,首先进行两个条件的检查:

    • isFlushingfalse:表示没有正在刷新任务队列,确保不会同时执行多次刷新。
    • isFlushPendingfalse:表示没有挂起的刷新请求,确保不会重复触发刷新。
  3. 如果上述两个条件都满足,就会执行以下操作:

    • isFlushPending 设置为 true,表示有一个刷新请求挂起。
    • 创建一个 Promise 对象 currentFlushPromise,使用 resolvedPromise 来生成一个已经 resolved(已完成)状态的 Promise,并通过 .then 方法将 flushJobs 函数作为回调传递进去。这样,flushJobs 函数会在下一个微任务中执行。
  4. currentFlushPromise 这个 Promise 被 resolved 时,会触发 flushJobs 函数的执行,从而实际执行任务刷新的操作。

总结:queueFlush 函数用于触发任务刷新,但会确保在没有正在刷新任务队列的情况下,且没有挂起的刷新请求时才触发。核心就是通过将刷新操作包装成 Promise,把 flushJobs 放入下一个微任务中执行,以确保任务刷新是异步的,不会阻塞当前 JavaScript 执行栈。这种设计有助于保持 Vue 3 的响应性和性能。

function flushJobs(seen?: CountMap) {
  // 标志当前没有挂起的刷新请求
  isFlushPending = false
  // 标志当前正在执行刷新操作
  isFlushing = true
  if (__DEV__) {
    // 如果是开发环境,创建一个用于追踪任务数量的 Map 对象
    seen = seen || new Map()
  }

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child.
  // 2. If a component is unmounted during a parent component's update,
  //    its update can be skipped.
  // 在执行刷新前,对任务队列进行排序,以确保以下两点:
  // 1. 组件更新的顺序是从父组件到子组件的(因为父组件总是在子组件之前创建,所以其渲染效果具有较小的优先级编号)。
  // 2. 如果在父组件的更新期间卸载了子组件,可以跳过子组件的更新。

  queue.sort(comparator)

  // 条件使用 checkRecursiveUpdate 必须在 try...catch 块外部决定
  // 因为 Rollup 默认会在 try...catch 块内部取消树摇,这可能导致所有警告代码都无法被摇掉。
  // 尽管它们最终会被像 terser 这样的压缩器摇掉,但某些压缩器可能无法做到这一点(例如 https://github.com/evanw/esbuild/issues/1610)。
  const check = __DEV__
    ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job)
    : NOOP

  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex]
      if (job && job.active !== false) {
        if (__DEV__ && check(job)) {
          continue
        }
        // 执行任务,并使用 callWithErrorHandling 包装,以捕获可能的错误
        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
      }
    }
  } finally {
    // 清空 flushIndex,重置任务队列
    flushIndex = 0
    queue.length = 0

    // 执行在刷新后的回调函数
    flushPostFlushCbs(seen)

    // 标志当前刷新结束
    isFlushing = false
    // 将当前刷新的 Promise 置为 null
    currentFlushPromise = null

    // 如果任务队列中仍有任务或者有挂起的 postFlush 回调
    // 继续执行刷新操作,直到任务队列为空
    if (queue.length || pendingPostFlushCbs.length) {
      flushJobs(seen)
    }
  }
}

这段代码是 Vue 3 中用于执行任务刷新的关键逻辑,它会在 queueFlush 函数触发时执行。

解释如下:

  1. flushJobs 函数用于执行任务刷新,它在 queueFlush 函数触发后被调用。

  2. 首先,它会将 isFlushPending 置为 false,表示当前没有挂起的刷新请求,然后将 isFlushing 置为 true,表示当前正在执行刷新操作。

  3. 在开发环境中,创建一个用于追踪任务数量的 seen Map 对象。

  4. 对任务队列 queue 进行排序,确保组件的更新按从父到子的顺序执行,同时也考虑了组件在父组件更新期间被卸载的情况。

  5. 使用 check 函数来检查是否存在递归更新的情况。这个检查在开发环境下才会执行。

  6. 进入循环,依次处理任务队列 queue中的任务。如果任务是激活状态且没有递归更新的情况,就执行该任务。callWithErrorHandling 函数用于包装任务的执行,以捕获可能的错误,其实就是包装了 job 函数,并且会把错误抛出。

  7. finally 块中,清空 flushIndex,重置任务队列 queue,确保任务只会执行一次。

  8. 执行在刷新后的回调函数 flushPostFlushCbs,这些回调函数通常用于清理或执行一些不同的调度政策。

  9. 最后,将 isFlushing 置为 false,表示刷新操作结束,将当前刷新的 Promise currentFlushPromise 置为 null

  10. 如果任务队列 queue 中仍有任务,或者有挂起的 postFlush 回调函数,就继续执行刷新操作,直到任务队列为空。

总结:flushJobs 函数是 Vue 3 中执行任务刷新的核心逻辑,它会在 queueFlush 触发后执行,负责遍历并执行任务队列中的任务,确保组件更新按正确的顺序执行,并处理递归更新的情况。这个函数的设计是为了保持 Vue 3 的响应性和性能。

比如我们可以在父组件中嵌套一个子组件,让父组件和子组件都输出 getCurrentInstance(上面提到过~)。可以看到 uid 的顺序就是先创建父组件,再创建子组件。

在这里插入图片描述

至于上面说的 flushPostFlushCbs 函数,其实我们在学watch的时候已经接触过了。

watch((xxx)=> {

},{
  flush: 'post'
})

这里的 flush: 'post' 其实就是调用了flushPostFlushCbs 函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1039634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023-09-25 LeetCode每日一题(LFU 缓存)

2023-09-25每日一题 一、题目编号 460. LFU 缓存二、题目链接 点击跳转到题目位置 三、题目描述 请你为 最不经常使用&#xff08;LFU&#xff09;缓存算法设计并实现数据结构。 实现 LFUCache 类&#xff1a; LFUCache(int capacity) - 用数据结构的容量 capacity 初始…

全流量安全分析之服务器非法外连

服务器非法外连监控的重要性在于保护服务器的安全性和防止被黑客利用&#xff0c;以下是几个重要的理由&#xff1a; 1、发现恶意活动&#xff1a;通过监控服务器的外连流量&#xff0c;可以及时发现是否有未经授权或可疑的连接尝试。这可能包括入侵攻击、数据泄露、恶意软件传…

基于微信小程序的家校通系统设计与实现(亮点:选题新颖、上传作业、批改作业、成绩统计)

文章目录 前言运行环境说明家长微信小程序端的主要功能有&#xff1a;教师微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文…

MySQL 索引底层 B+Tree 原理解析

目录 一、前言二、B-Tree 和 BTree 的区别三、InnoDB 和 MyISAM 存储引擎索引存储区别MyISAMInnoDB 四、InnoDB 联合索引底层数据结构五、MySQL 中三次磁盘IO最大能检索多少数据 一、前言 索引是帮助高效获取数据排好序的数据结构&#xff0c;任何数据库都会使用到索引&#x…

漂流伞顾曼宁(顾曼)的创业之路:相信的力量

顾曼宁&#xff08;顾曼&#xff09;的心情在蚂蚁生态全球CEO大会上充满了感动和鼓舞。这是一个期待已久的盛会&#xff0c;时隔三年&#xff0c;蚂蚁生态全球的首席执行官们再次汇聚一堂。智慧与商业的碰撞下&#xff0c;更藏着感情与坚持的交汇。 蚂蚁集团举办了【相信展】的…

求生之路2服务器搭建插件安装及详细的游戏参数配置教程linux

求生之路2服务器搭建插件安装及详细的游戏参数配置教程linux 大家好我是艾西&#xff0c;在上一篇文章中我用windows系统给搭建演示了一遍怎么搭建自己的L4D2游戏。 那么也有不少小伙伴想知道linux系统的搭建方式以及在这个过程中有什么区别。 那么艾西今天就跟大家分享下用lin…

Nginx可视化管理工具结合cpolar实现远程访问内网服务

前言 Nginx Proxy Manager 是一个开源的反向代理工具&#xff0c;不需要了解太多 Nginx 或 Letsencrypt 的相关知识&#xff0c;即可快速将你的服务暴露到外部环境&#xff0c;并且支持 SSL 配置。基于 Tabler 的美观且安全的管理界面,无需了解 Nginx 即可轻松创建转发域、重定…

生产看板管理系统助力车间生产线提升产量、质量

随着制造业的发展和市场竞争的加剧&#xff0c;企业需要寻求创新的生产管理解决方案来提高生产效率和质量。而车间生产看板管理系统正是满足这一需求的理想选择。这种系统通过实时监控生产线上的各项指标&#xff0c;帮助企业及时发现生产问题&#xff0c;并采取相应的措施。通…

【送书活动】《客户成功的力量》——客户成功体系如何构建?请看这7步

文章目录 前言当下客户成功的痛点客户成功体系构建七步法作者简介赠书活动 前言 在中国企业服务领域的发展过程中&#xff0c;客户成功从在中国萌芽开始&#xff0c;行业内外对其讨论几乎没有停止过。近段时间&#xff0c;关于客户成功的讨论再次被业内广泛关注&#xff0c;原因…

微力私人网盘通过cpolar端口映射,成功实现远程访问本地电脑!

文章目录 1.前言2. 微力同步网站搭建2.1 微力同步下载和安装2.2 微力同步网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 私有云盘作为云存储概念的延伸&#xff0c;虽然谈不上多么新颖&#xff0c;但是其…

遥遥领先-华为发布会重磅来袭!我的梦,刘德华...

本次发布会一共涉及了三个头号人物&#xff0c;分别是 1.余承东 2.刘德华 3.何刚 对于很多伙伴期待的手机&#xff0c;本次发布会是没有登场的&#xff0c;也有部分伙伴猜测华为是由于部分原因没有登场。 相信后期将会有有更大的策划和科技登场~ “穿过了黑夜&#xff0c;…

【C语言干货】一秒钟记住52个字母的ASCII码

一、ASCII的介绍 ​​ASCII (American Standard Code for Information Interchange)&#xff1a;美国信息交换标准代码是基于拉丁字母的一套电脑编码系统&#xff0c;主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准&#xff0c;并等同于国际标准 ISO/IEC 646。…

ubuntu 14.04更新GCC版本

按最基本的apt-get install gcc-8&#xff0c;不成功&#xff0c;提示如下。 按网上说的&#xff1a;apt-get update ,apt-get upgrade 后都无效果。 apt-cache search get 搜索后&#xff0c;发现资源链接里最新的也只有4.8.4所以不行。 需要更新资源链接&#xff0c;镜像地…

uni-app:实现背景渐变效果

效果 代码 单个渐变色 background-image: linear-gradient(to top right, #f00, #00f); 多个渐变色 background-image: linear-gradient(to bottom, #0073ff 0%, #1d6cff 25%, #1e8bff 50%, #41b3ff 100%); /* 多个渐变色 */ <template><view></view> <…

【Vue.js】使用Element搭建首页导航左侧菜单

目录 Mock.js 是什么 有什么好处 安装mockjs ​编辑 引入mockjs mockjs使用 login-mock Bus事物总线 首页导航栏与左侧菜单搭建 结合总线完成组件通讯 Mock.js 是什么 Mock.js是一个用于生成随机数据的模拟数据生成器。它可以帮助开发人员模拟接口请求&#xff0c;生…

轻松几步操作,视频边框背景虚化无压力

在视频剪辑的世界中&#xff0c;许多人都苦于繁琐的操作和复杂的流程。然而&#xff0c;如果你掌握了正确的工具和方法&#xff0c;许多复杂的任务可以变得简单高效。今天&#xff0c;我将分享一个简单实用的视频剪辑技巧&#xff0c;让你轻松完成批量剪辑视频并虚化边框的快速…

常识判断 --- 党史

目录 中共1~3大 例题 国民党 例题 中共4~5大 例题 中共起义~会议 例题 中共六届六中全会&#xff08;1938年9月&#xff09; 中共七大&#xff08;1945年4月&#xff09; 例题 中共七届二中全会 例题 中共8~10大 中共11~12届全会 例题 中共13~14大 …

基于物联网的农村地区智能微电网系统(Simulink)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Generative AI 新世界 | 扩散模型原理的代码实践之采样篇

在上一期的文章中&#xff0c;探讨了在 Amazon SageMaker Studio 上使用 QLoRA 等量化技术微调 Falcon 40B 大语言模型。而从本期开始&#xff0c;我们将一起尝试在更深的知识维度&#xff0c;继续探究生成式 AI 这一火热的新知识领域。 亚马逊云科技开发者社区为开发者们提供全…

Leetcode 877. 石子游戏

文章目录 题目代码&#xff08;9.23 首刷看解析&#xff09; 题目 Leetcode 877. 石子游戏 代码&#xff08;9.23 首刷看解析&#xff09; 博弈 class Solution { public:bool stoneGame(vector<int>& piles) {int len piles.size();vector<vector<int>…