【Vue3 源码讲解】nextTick

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 || resolvedPromisereturn 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 = trueif (__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): NOOPtry {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 = 0queue.length = 0// 执行在刷新后的回调函数flushPostFlushCbs(seen)// 标志当前刷新结束isFlushing = false// 将当前刷新的 Promise 置为 nullcurrentFlushPromise = 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.mzph.cn/news/84494.shtml

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

相关文章

websocket php教程

WebSocket 是 HTML5 提供的一种网络通讯协议&#xff0c;用于服务端与客户端实时数据传输。广泛用于浏览器与服务器的实时通讯&#xff0c;APP与服务器的实时通讯等场景。 相比传统HTTP协议请求响应式通讯&#xff0c;WebSocket协议可以做到实时的双向通讯&#xff0c;服务端可…

Webpack打包图片

一、在js文件中引入图片 二、在package.config.js中配置加载器 module.exports {mode: "production", // 设置打包的模式&#xff1a;production生产模式 development开发模式module: {rules: [// 配置img加载器{test: /\.(jpg|png|gif)$/i,type:"asset/resou…

Hadoop-sqoop

sqoop 1. Sqoop简介及原理 简介&#xff1a; Sqoop是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysq1.postgresql..)间进行数据的传递&#xff0c;可以将一个关系型数据库&#xff08;例如: MySQL ,Oracle ,Postgres等&#xff09;中的数据导进到Hadoop 的HDFS中&…

虚拟机中window/ubuntu系统如何联网?

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 参考博客 &#xff08;1&#xff09;VMware虚拟机中Windows11无法连接网络 &#xff08;2&#xff09;图解vmware虚拟机win8无线上网 &#xff08;3&#xff09;VMware中VMnet0、VMnet1、VMnet8是什么 &…

python教程:使用gevent实现高并发并限制最大并发数

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 import time import gevent from gevent.pool import Pool from gevent import monkey # 一&#xff0c;定义最大并发数 p Pool(20) # 二&#xff0c;导入gevent…

Flink1.12.7 Standalone版本安装

官网下载版本&#xff1a;https://archive.apache.org/dist/flink/flink-1.12.7/flink-1.12.7-bin-scala_2.12.tgz 可以从首页找到Downloads | Apache Flink&#xff0c;一直往下拉 安装&#xff1a;下载后直接解压即可 添加全局参数&#xff1a; #vi /etc/profile FLINK_HO…

数据结构之堆的结构与实现

目录 一、堆的概念及结构 1.1堆的概念 1.2堆的性质 1.3堆的结构 二、堆的实现 2.1堆向下调整算法&#xff08;父亲与孩子做比较&#xff09; 2.2堆的向上调整算法&#xff08;孩子与父亲做比较&#xff09; 2.3堆的创建&#xff08;向下建堆&#xff09; 2.4向下建堆的时…

GitHub上标星23K+的Redis进阶笔记(应用+原理+集群+拓展+源码)

先来看几道Redis面试题&#xff1a; redis为什么是key&#xff0c;value的&#xff0c;为什么不是支持SQL的&#xff1f;redis是多线程还是单线程&#xff1f;&#xff08;回答单线程的请回吧&#xff0c;为什么请回&#xff0c;请往下看&#xff09;redis的持久化开启了RDB和…

opencv实现仿射变换

什么是仿射变换&#xff1f; 代码实现 import numpy as np import cv2 as cv import matplotlib.pyplot as plt#设置字体 from pylab import mpl mpl.rcParams[font.sans-serif] [SimHei]#图像的读取 img cv.imread("lena.png")#仿射变换 rows , cols img.shape[…

C/C++空格分开输出 2019年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C空格分开输出 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C空格分开输出 2019年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 读入一个字符&#xff0c;一个整…

校园学习《乡村振兴战略下传统村落文化旅游设计》 许少辉瑞博士生辉少许

校园学习《乡村振兴战略下传统村落文化旅游设计》 许少辉瑞博士生辉少许

9 种方法使用 Amazon CodeWhisperer 快速构建应用

Amazon CodeWhisperer 是一款很赞的生成式人工智能编程工具。自从在工作中使用了 CodeWhisperer&#xff0c;我发现不仅代码编译的效率有所提高&#xff0c;应用开发的工作也变得快乐起来。然而&#xff0c;任何生成式 AI 工具的有效学习都需要初学者要有接受新工作方式的心态和…

Python 基于PyCharm断点调试

视频版教程 Python3零基础7天入门实战视频教程 PyCharm Debug&#xff08;断点调试&#xff09;可以帮助开发者在代码运行时进行实时的调试和错误排查&#xff0c;提高代码开发效率和代码质量。 准备一段代码 def add(num1, num2):return num1 num2if __name__ __main__:f…

redis深度历险 千帆竞发 —— 分布式锁

分布式应用进行逻辑处理时经常会遇到并发问题。 比如一个操作要修改用户的状态&#xff0c;修改状态需要先读出用户的状态&#xff0c;在内存里进行修改&#xff0c;改完了再存回去。如果这样的操作同时进行了&#xff0c;就会出现并发问题&#xff0c;因为读取和保存状态这两个…

Cpp/Qt-day050921Qt

目录 实现使用数据库的登录注册功能 头文件&#xff1a; registrwidget.h: widget.h: 源文件&#xff1a; registrwidget.c: widget.h: 效果图&#xff1a; 思维导图 实现使用数据库的登录注册功能 头文件&#xff1a; registrwidget.h: #ifndef REGISTRWIDGET_H #de…

ChatGPT实战-构建文章分析AI聊天机器人

视频版本&#xff1a; ChatGPT实战-构建文章分析AI聊天机器人 简介 本文实现如下功能&#xff1a; 当浏览一篇文章&#xff0c;点击分享&#xff0c;分享到聊天软件的对话框中。它就会生成一个文章的总结和分析结果。例如分析是否有逻辑问题&#xff0c;是否有诱导购买&#…

Fiddler 八个实用技巧

大家对Fiddler应该不会陌生&#xff0c;但里面有些技巧不见得都会&#xff0c;这里就有八个实用技巧&#xff0c;通过对Fiddler的定制&#xff0c;能提高大家的测试效率。 1、双击Session时&#xff0c;使响应页始终显示到”json”tab页&#xff1b;使请求页始终显示到“webfo…

【操作系统】聊聊什么是CPU上下文切换

对于linux来说&#xff0c;本身就是一个多任务运行的操作系统&#xff0c;运行远大于CPU核心数的程序&#xff0c;从用户视角来看是并发执行&#xff0c;而在CPU视角看其实是将不同的CPU时间片进行分割&#xff0c;每个程序执行一下&#xff0c;就切换到别的程序执行。那么这个…

Controller统一异常处理和yaml配置

目录 Controller统一异常处理 url解析 static下静态资源文件的访问 配置类 如何访问static下的资源文件 yaml基础语法 注解赋值 批量注入 单个注入 Controller统一异常处理 Controller统一异常处理ControllerAdvice&#xff1a;统一为Controller进行"增强" …

(Clock Domain Crossing)跨时钟域信号的处理 (自我总结)

CummingsSNUG2008Boston_CDC.pdf 参考&#xff1a; 跨时钟域处理方法总结–最终详尽版 - love小酒窝 - 博客园 跨时钟域&#xff08;CDC&#xff09;设计方法之单bit信号篇&#xff08;一&#xff09; | 电子创新网赛灵思社区 孤独的单刀_Verilog语法,FPGA设计与调试,FPGA接口与…