【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>
解释如下:
-
函数签名:
nextTick<T = void, R = void>(this: T, fn?: (this: T) => R): Promise<Awaited<R>>
:这是nextTick
函数的类型签名。它接受以下参数:this
:不是参数,是ts中的一个语法,给 this 定义类型。给用于绑定回调函数中的this
上下文,可以省略。fn
:要异步执行的回调函数,是一个函数,可以省略。
- 函数返回一个
Promise
,Promise
的泛型为Awaited<R>
,表示回调函数执行后的返回值。
-
实现逻辑:
-
nextTick
函数首先定义了一个Promise
对象p
,这个Promise
对象被初始化为currentFlushPromise
或resolvedPromise
,取决于当前是否处于刷新队列(flush
)的过程中。currentFlushPromise
是一个全局变量,用于表示当前正在刷新队列,如果不在刷新队列中,则使用resolvedPromise
,它是一个已经 resolved(已解决)的Promise
。 -
接着,函数检查是否传入了回调函数
fn
。如果传入了fn
,则会返回一个Promise.then()
,去执行一个微任务,这个Promise
在微任务队列中执行回调函数(如果使用await nextTick()
也相当于执行一个微任务)。如果没有传入fn
,则直接返回p
,这意味着如果没有回调函数,会返回一个已经 resolved 的Promise
。 -
在返回
Promise
的过程中,函数会根据传入的this
上下文(如果有的话),使用fn.bind(this)
来绑定回调函数中的this
,确保回调函数在执行时具有正确的上下文。
-
-
总结:
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 内部用于调度和执行任务的关键部分,主要涉及到任务队列的管理和触发任务执行的逻辑。
解释如下:
-
queueJob
函数用于将一个任务job
添加到任务队列queue
中,并触发任务刷新(queueFlush
)。 -
在添加任务之前,首先会进行一些条件判断:
- 首先检查任务队列是否为空,如果为空,直接将任务添加到队列中,不需要进行后续的检查。
- 然后,使用
queue.includes
方法检查当前任务job
是否已经在任务队列中。这里的检查是为了避免任务的重复添加。如果任务队列中已经包含了当前任务,就不再添加。
-
如果当前任务
job
通过了上述检查条件,就可以将它添加到任务队列中:- 如果任务的
id
属性为null
,表示没有指定任务的唯一标识,直接将任务添加到队列末尾(queue.push(job)
)。 - 如果任务的
id
属性不为null
,表示指定了任务的唯一标识,需要根据该标识找到合适的插入位置,以保持任务队列中的任务按照id
的顺序执行(按照 job id 自增的顺序排列)。这时会调用findInsertionIndex(job.id)
来找到插入位置,然后使用splice
方法插入任务到指定位置。
- 如果任务的
-
最后,调用
queueFlush
函数,触发任务刷新。任务刷新会开始执行任务队列中的任务。
总结:queueJob
函数用于添加任务到任务队列中,并确保任务不会重复添加。它也负责触发任务刷新,以开始执行任务队列中的任务。这是 Vue 3 中任务调度的核心逻辑之一,用于管理异步任务的执行顺序。
以上的 id
和 job
都存在于 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
)的逻辑。它确保在没有正在刷新任务队列(isFlushing
为 false
)且没有挂起的刷新请求(isFlushPending
为 false
)时才会触发任务刷新。
解释如下:
-
queueFlush
函数用于触发任务刷新,但它不会立即执行刷新操作,而是通过 Promise 的方式进行延迟执行。 -
在执行触发刷新前,首先进行两个条件的检查:
isFlushing
为false
:表示没有正在刷新任务队列,确保不会同时执行多次刷新。isFlushPending
为false
:表示没有挂起的刷新请求,确保不会重复触发刷新。
-
如果上述两个条件都满足,就会执行以下操作:
- 将
isFlushPending
设置为true
,表示有一个刷新请求挂起。 - 创建一个 Promise 对象
currentFlushPromise
,使用resolvedPromise
来生成一个已经 resolved(已完成)状态的 Promise,并通过.then
方法将flushJobs
函数作为回调传递进去。这样,flushJobs
函数会在下一个微任务中执行。
- 将
-
当
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
函数触发时执行。
解释如下:
-
flushJobs
函数用于执行任务刷新,它在queueFlush
函数触发后被调用。 -
首先,它会将
isFlushPending
置为false
,表示当前没有挂起的刷新请求,然后将isFlushing
置为true
,表示当前正在执行刷新操作。 -
在开发环境中,创建一个用于追踪任务数量的
seen
Map 对象。 -
对任务队列
queue
进行排序,确保组件的更新按从父到子的顺序执行,同时也考虑了组件在父组件更新期间被卸载的情况。 -
使用
check
函数来检查是否存在递归更新的情况。这个检查在开发环境下才会执行。 -
进入循环,依次处理任务队列
queue
中的任务。如果任务是激活状态且没有递归更新的情况,就执行该任务。callWithErrorHandling
函数用于包装任务的执行,以捕获可能的错误,其实就是包装了 job 函数,并且会把错误抛出。 -
finally
块中,清空flushIndex
,重置任务队列queue
,确保任务只会执行一次。 -
执行在刷新后的回调函数
flushPostFlushCbs
,这些回调函数通常用于清理或执行一些不同的调度政策。 -
最后,将
isFlushing
置为false
,表示刷新操作结束,将当前刷新的 PromisecurrentFlushPromise
置为null
。 -
如果任务队列
queue
中仍有任务,或者有挂起的postFlush
回调函数,就继续执行刷新操作,直到任务队列为空。
总结:flushJobs
函数是 Vue 3 中执行任务刷新的核心逻辑,它会在 queueFlush
触发后执行,负责遍历并执行任务队列中的任务,确保组件更新按正确的顺序执行,并处理递归更新的情况。这个函数的设计是为了保持 Vue 3 的响应性和性能。
比如我们可以在父组件中嵌套一个子组件,让父组件和子组件都输出 getCurrentInstance
(上面提到过~)。可以看到 uid 的顺序就是先创建父组件,再创建子组件。
至于上面说的 flushPostFlushCbs
函数,其实我们在学watch
的时候已经接触过了。
watch((xxx)=> {},{flush: 'post'
})
这里的 flush: 'post'
其实就是调用了flushPostFlushCbs
函数。
相关文章:

【Vue3 源码解析】nextTick
nextTick 是 Vue 3 中用于异步执行回调函数的函数,它会将回调函数延迟到下一个微任务队列中执行。其中,Vue 更新 DOM 是异步的。下面是对 nextTick 函数的详细解释: export function nextTick<T void, R void>(this: T,fn?: (this:…...

基于ModebusRTU通信采集温度湿度项目案例
目录 一、模拟温湿度模拟 【1.1】温湿度仪表参数 【1.1】使用电脑模拟传感器 【1.2】使用Codesys软件模拟传感器 二、自定义控件UI设计 【2.1】自定义控件温度湿度柱状设计 编辑 【2.1.1】设置温度湿度柱状实际显示【属性】 【2.1.2】设置温度湿度柱状的背景颜色【属…...

【已解决】关于如何将Doccano标注的文本转换成NER模型可以直接处理的CoNLL 2003格式
笔者要做命名实体识别(NER)的工作,选择了Doccano平台来进行文本标注。 Doccano平台对标注结果的导出格式是JSONL格式,我们导出了NER.jsonl文件。 但是用python语言搭建深度学习模型来实现NER时,一般接收的输入数据格式…...

网络编程day03(UDP中的connect函数、tftp)
今日任务:tftp的文件上传下载(服务端已经准备好) 服务端(已上传) 客户端: 代码: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h…...

flarum 论坛 User Statistics插件修改
此插件在中国使用日期不是很理想,于是决定修改代码 下面是插件信息: User Statistics A Flarum extension. Add some user statistics in flarum posts, this extension require clarkwinkelmann/flarum-ext-likes-received and will be installed au…...

阿里云产品试用系列-容器镜像服务 ACR
阿里云容器镜像服务(简称 ACR)是面向容器镜像、Helm Chart 等符合 OCI 标准的云原生制品安全托管及高效分发平台。 ACR 支持全球同步加速、大规模/大镜像分发加速、多代码源构建加速等全链路提效,与容器服务 ACK 无缝集成,帮助企业…...
Langchain里的“记忆力”,让AI只记住有用的事
今天要讲以下内容: 1.ConversationBufferWindowMemory:基于一个固定长度的滑动窗口的“记忆”功能 2.ConversationSummaryMemory:总结对话“记忆”功能 3.ConversationSummaryBufferMemory:上面两个的结合,超过一定…...

从零开始的LINUX(一)
LINUX本质是一种操作系统,用于对软硬件资源进行管理,其管理的方式是指令。指令是先于图形化界面产生的,相比起图形化界面,指令显然更加难以理解,但两者只是形式上的不同,本质并没有区别。 简单的指令&…...

CH34X-MPHSI高速Master扩展应用—I2C设备调试
一、前言 本文介绍,基于USB2.0高速USB转接芯片CH347,配合厂商提供的USB转MPHSI(Multi Protocol High-Speed Serial Interface)Master总线驱动(CH34X-MPHSI-Master)为系统扩展I2C总线的用法,除…...
记一次正式环境升级docker服务基础进行版本异常
因为服务的httpd和tomcat基础镜像版本比较旧,漏洞多,需要升级至最新版本。在本地环境和测试环境都是直接将dockerfile中的FROM基础镜像升级至最新: httpd:由httpd:2.4.52-alpine升级至httpd:2.4.57 tomcat:由4年前的…...

leetcode面试经典150题第一弹(一)
leetcode面试经典150题第一弹 一、合并俩个有序数组(难度:简单) 题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并…...

VME-7807RC-414001 350-93007807-414001 VMIVME-017807-411001 VMIVME-017807-414001
VME-7807RC-414001 350-93007807-414001 VMIVME-017807-411001 VMIVME-017807-414001 由于第12代英特尔酷睿处理器的16核/24线程配置,Nuvo-9000型号与之前的平台相比,性能大幅提升。它们还支持新的DDR5内存标准,以获得更多内存带宽…...

01-Zookeeper特性与节点数据类型详解
上一篇: 在了解Zookeeper之前,需要对分布式相关知识有一定了解,什么是分布式系统呢?通常情况下,单个物理节点很容易达到性能,计算或者容量的瓶颈,所以这个时候就需要多个物理节点来共同完成某项…...
TP6 TP8 使用阿里官方OSS SDK方法
安装 composer require aliyuncs/oss-sdk-php 官网:GitHub - aliyun/aliyun-oss-php-sdk: Aliyun OSS SDK for PHP 二、PHP简单上传 官网教程:如何进行字符串上传和文件上传_对象存储 OSS-阿里云帮助中心 引入 use OSS\OssClient; use OSS\Core\OssE…...

SkyWalking分布式链路追踪学习
为什么要用分布式链路追踪 实际生产中,面对几十个、甚至成百上千个的微服务实例,如果一旦某个实例发生宕机,如果不能快速定位、提交预警,对实际生产造成的损失无疑是巨大的。所以,要对微服务进行监控、预警࿰…...

git revert 撤销之前的提交
git revert 用来撤销之前的提交,它会生成一个新的 commit id 。 输入 git revert --help 可以看到帮忙信息。 git revert commitID 不编辑新的 commit 说明 git log 找到需要撤销的 commitID , 然后执行 git revert commitID ,会提示如下…...

rk3568环境配置和推理报错: RKNN_ERR_MALLOC_FAIL
前言 最近在部署算法在板子侧遇到的一些问题汇总一下: 一、版本问题 经过测试现在将自己环境配置如下: 本地linux安装rknn-toolkit2-1.5.0 本地Linux使用的miniconda新建的一个python虚拟环境(自行网上查找相关方法) 安装好自…...
网络工程师基础笔记(一)
一、接入网 接入网,是指将端系统物理连接到边缘路由器的网络。 (1)家庭接入:数字用户线(DSL)、电缆、光纤到户(FTTH)卫星和拨号接入。 (2)企业(家庭)接入&…...

Postman应用——Headers请求头设置
文章目录 Header设置Header删除或禁用Header批量编辑Header预设添加 一般在接口需要校验签名时,Headers请求头用来携带签名和生成签名需要的参数,在Postman也可以设置请求头在接口请求时携带参数。 Header设置 说明: Key:Header…...

人人都是项目经理-项目管理概述(一)
一、重新认识项目管理 1. 什么是项目 项目(Project),是为提供某项独特的产品(交付物),服务或成果所做的临时性努力。 – PMBOK指南 项目是指一系列独特的、复杂的并相互关联的活动,这些活动有着…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...