nextTick源码解读

📝个人主页:爱吃炫迈
💌系列专栏:Vue
🧑‍💻座右铭:道阻且长,行则将至💗

文章目录

  • nextTick原理
    • nextTick
    • timerFunc
    • flushCallbacks
  • 异步更新流程
    • update
    • queueWatcher
    • flushSchedulerQueue
    • resetSchedulerState


nextTick原理

nextTick

export let isUsingMicroTask = false // 标记 nextTick 最终是否以微任务执行
/*存放异步执行的回调*/
const callbacks = [] 
/*一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送*/
let pending = false
/*一个函数指针,指向函数将被推送到任务队列中,等到主线程任务执行完时,任务队列中的timerFunc被调用*/
let timerFunc/*推送到队列中下一个tick时执行cb 回调函数ctx 上下文
*/
export function nextTick (cb?: Function, ctx?: Object) {let _resolve// 第一步 传入的cb会被push进callbacks中存放起来callbacks.push(() => {if (cb) {      try {cb.call(ctx)} catch (e) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})// 第二步:判断用什么方法// 检查上一个异步任务队列(即名为callbacks的任务数组)是否派发和执行完毕了。pending此处相当于一个锁if (!pending) {// 若上一个异步任务队列已经执行完毕,则将pending设定为true(把锁锁上)pending = true// 调用判断Promise,MutationObserver,setTimeout的优先级timerFunc()}// 第三步:nextTick 函数会返回一个Promise对象。该Promise对象在异步任务执行完毕后会resolve,可以让用户在异步任务执行完毕后进行处理。if (!cb && typeof Promise !== 'undefined') {   return new Promise(resolve => {_resolve = resolve})}
}

解释

第二步pending 的作用就是一个锁,防止后续的 nextTick 重复执行 timerFunc(换句话说:当在同一轮事件循环中多次调用 nextTick 时 ,timerFunc 只会执行一次)。timerFunc 内部创建会一个微任务或宏任务,等待所有的 nextTick 同步执行完成后,再去执行 callbacks 内的回调。

timerFunc

💡 timerFunc函数,主要通过一些兼容判断来创建合适的 timerFunc,最优先肯定是微任务,其次再到宏任务。 优先级为 promise.then > MutationObserver > setImmediate > setTimeout

// 判断当前环境是否原生支持 promise
if (typeof Promise !== 'undefined' && isNative(Promise)) { // 支持 promiseconst p = Promise.resolve()timerFunc = () => {// 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务p.then(flushCallbacks)if (isIOS) setTimeout(noop)}// 标记当前 nextTick 使用的微任务isUsingMicroTask = true// 如果不支持 promise,就判断是否支持 MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||MutationObserver.toString() === '[object MutationObserverConstructor]')) {let counter = 1const observer = new MutationObserver(flushCallbacks)const textNode = document.createTextNode(String(counter))observer.observe(textNode, {characterData: true})timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter) // 数据更新}isUsingMicroTask = true // 标记当前 nextTick 使用的微任务// 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {timerFunc = () => {setImmediate(flushCallbacks)}// 以上三种都不支持就选择 setTimeout
} else {timerFunc = () => {setTimeout(flushCallbacks, 0)}
}

我们发现无论那种timerFunc 最终都会执行flushCallbacks 函数

flushCallbacks

💡 flushCallbacks 里做的事情很简单,它就负责执行 callbacks 里的回调。

// flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {pending = falseconst copies = callbacks.slice(0) // 拷贝一份 callbackscallbacks.length = 0 // 清空 callbacksfor (let i = 0; i < copies.length; i++) { // 遍历执行传入的回调copies[i]()}
}

image-20230929174246795

异步更新流程

Vue 使用异步更新,等待所有数据同步修改完成后,再去执行更新逻辑。

update

💡
触发某个数据的setter方法后,它的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。

/*调度者接口,当依赖发生改变的时候进行回调 */update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true} else if (this.sync) {/*同步则执行run直接渲染视图*/this.run()} else {/*异步推送到观察者队列中,下一个tick时调用。*/queueWatcher(this) // this为当前实例watcher}}

queueWatcher

💡 将一个观察者对象push进观察者队列,在队列中已经存在相同的id则该观察者对象将被跳过,除非它是在队列被刷新时推送

export function queueWatcher (watcher: Watcher) {/*获取watcher的id*/const id = watcher.id/*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/if (has[id] == null) {has[id] = true// 不是刷新if (!flushing) {queue.push(watcher)  // 将多个渲染watcher去重后放到队列中} else {// if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.let i = queue.length - 1while (i >= 0 && queue[i].id > watcher.id) {i--}queue.splice(Math.max(i, index) + 1, 0, watcher)}// 是刷新if (!waiting) {waiting = truenextTick(flushSchedulerQueue) //这里会产生一个nextTick,队列刷新函数(flushSchedulerQueue)}}
}

从queueWatcher代码中看出Watch对象并不是立即更新视图,而是被push进了一个队列queue,此时状态处于waiting的状态,这时候会继续会有Watch对象被push进这个队列queue,等到下一个tick运行时将这个队列queue全部拿出来run一遍,这些Watch对象才会被遍历取出,更新视图。同时,id重复的Watcher不会被多次加入到queue中去。这也解释了同一个watcher被多次触发,只会被推入到队列中一次。

flushSchedulerQueue

💡 flushSchedulerQueue 内将刚刚加入 queuewatcher 逐个 run 更新。

function flushSchedulerQueue () {currentFlushTimestamp = getNow()flushing = truelet watcher, id// 在刷新之前对队列进行排序。// 这确保了:// 1. 组件从父级更新到子级。(因为父母总是在子进程之前创建)// 2. 组件的用户观察程序在其渲染观察程序之前运行(因为用户观察者是在渲染观察者之前创建的)// 3. 如果组件在父组件的观察程序运行期间被销毁,可以跳过它的观察者。queue.sort((a, b) => a.id - b.id)// do not cache length because more watchers might be pushed// as we run existing watchersfor (index = 0; index < queue.length; index++) {watcher = queue[index]if (watcher.before) {watcher.before()}id = watcher.idhas[id] = nullwatcher.run()}// keep copies of post queues before resetting stateconst activatedQueue = activatedChildren.slice()const updatedQueue = queue.slice()resetSchedulerState()// call component updated and activated hookscallActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)
}

resetSchedulerState

💡 resetSchedulerState 重置状态,等待下一轮的异步更新。

function resetSchedulerState () {index = queue.length = activatedChildren.length = 0has = {}if (process.env.NODE_ENV !== 'production') {circular = {}}waiting = flushing = false
}

要注意此时 flushSchedulerQueue 还未执行,它只是作为回调传入而已。因为用户可能也会调用 nextTick 方法。这种情况下,callbacks 里的内容为 [“flushSchedulerQueue”, “用户的nextTick回调”],当所有同步任务执行完成,才开始执行 callbacks 里面的回调。

由此可见,最先执行的是页面更新的逻辑,其次再到用户的 nextTick 回调执行。这也是为什么我们能在 nextTick 中获取到更新后DOM的原因。

参考文章:

Vue你不得不知道的异步更新机制和nextTick原理 - 掘金

通俗易懂的Vue异步更新策略及 nextTick 原理 - 掘金

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

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

相关文章

ROS2 库包设置和使用 Catch2 进行单元测试

说明 本文的目的是了解如何在 ROS2 中创建库&#xff0c;以供其他 ROS2 包使用。除此之外&#xff0c;本文还介绍了如何使用 catch2 框架编写单元测试。本文的第 1 部分将详细介绍如何创建库包。第 2 部分将介绍 ROS2 软件包如何利用创建的库 上篇 ROS2 库包设置和使用 Catch2…

GEO生信数据挖掘(一)数据集下载和初步观察

检索到目标数据集后&#xff0c;开始数据挖掘&#xff0c;本文以阿尔兹海默症数据集GSE1297为例 目录 GEOquery 简介 安装并加载GEOquery包 getGEO函数获取数据&#xff08;联网下载&#xff09; 更换下载数据源 对数据集进行初步观察处理 GEOquery 简介 GEOquery是一个…

【AntDesign】封装全局异常处理-全局拦截器

[toc] 场景 本文前端用的是阿里的Ant-Design框架&#xff0c;其他框架也有全局拦截器&#xff0c;思路是相同&#xff0c;具体实现自行百度下吧 因为每次都需要调接口&#xff0c;都需要单独处理异常情况&#xff08;code !0&#xff09;&#xff0c;因此前端需要对后端返回的…

联邦学习-Tensorflow实现联邦模型AlexNet on CIFAR-10

目录 Client端 Server端 扩展 Client.py Server.py Dataset.py Model.py 分享一种实现联邦学习的方法&#xff0c;它具有以下优点&#xff1a; 不需要读写文件来保存、切换Client模型 不需要在每次epoch重新初始化Client变量 内存占用尽可能小&#xff08;参数量仅翻一…

1.4.C++项目:仿muduo库实现并发服务器之buffer模块的设计

项目完整版在&#xff1a; 一、buffer模块&#xff1a; 缓冲区模块 Buffer模块是一个缓冲区模块&#xff0c;用于实现通信中用户态的接收缓冲区和发送缓冲区功能。 二、提供的功能 存储数据&#xff0c;取出数据 三、实现思想 1.实现换出去得有一块内存空间&#xff0c;采…

Learning Invariant Representation for Unsupervised Image Restoration

Learning Invariant Representation for Unsupervised Image Restoration (Paper reading) Wenchao Du, Sichuan University, CVPR20, Cited:63, Code, Paper 1. 前言 近年来&#xff0c;跨域传输被应用于无监督图像恢复任务中。但是&#xff0c;直接应用已有的框架&#xf…

【python海洋专题三】图像修饰之画布和坐标轴

【python海洋专题三】图像修饰之画布和坐标轴 海洋与大气科学 上期读取nc水深文件&#xff0c;并出图 但是存在一些不完美&#xff0c;本期修饰 本期内容目录 1&#xff1a;改变画布大小 2&#xff1a;改变画布背景色 3&#xff1a;改变画布在显示屏中的显示位置 4&#xf…

【项目管理】--敏捷开发管理之Scrum

目录 一、前言二、what---敏捷开发是什么2.1、敏捷开发宣言2.2、敏捷开发原则2.3、一句话概述敏捷开发三、why---为什么会有敏捷开发3.1、传统开发模式和敏捷开发模式对比四、how---敏捷开发怎么实践到项目团队4.1、what---Scrum是什么4.2、what---Scrum有哪些内容(1)、Scrum之…

NLP 01(介绍)

一、NLP 自然语言处理 (Natural Language rrocessing,简称NLP) 是计算机科学与语言学中关注于计算机与人类语言间转换的领域。 1.1 发展 规则&#xff1a;基于语法 自然语言处理的应用场景: 语音助手 机器翻译 搜索引擎 智能问答

【单片机】12-串口通信和RS485

1.通信有关的常见概念 区分&#xff1a;串口&#xff0c;COM口&#xff0c;UART&#xff0c;USART_usart和串口区别-CSDN博客 串口、COM口、UART口, TTL、RS-232、RS-485区别详解-CSDN博客 1.什么是通信 &#xff08;1&#xff09;人和人之间的通信&#xff1a;说话&#xff…

抓包习讯云院校数据通过PHP解析导入数据库

前言 最近&#xff0c;打卡APP需要这个数据&#xff0c;通过抓包后发现这个数据是固定的&#xff0c;获取很简单&#xff0c;但是数据太多&#xff0c;手动导入不显示&#xff0c;于是分析了json格式后果断通过脚本完成 【推荐】 《【MQTT】Esp32数据上传采集&#xff1a;最…

栈和队列的概念和实现

栈和队列的概念和实现 一.栈二.队列一些题目 一.栈 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作&#xff0c;进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底&#xff0c;栈中的数据元素遵守后进先出LIFO&#x…

UG\NX二次开发 用程序修改“用户默认设置”

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 简介 可以用程序修改“用户默认设置”吗?下面是用代码修改“用户默认设置->基本环境->用户界面->操作记录->操作记录语言”的例子。 效果 代码 #include <uf_defs.h> #include <NXOpen/NXExcept…

angular 在vscode 下的hello world

Angulai 是google 公司开发的前端开发框架。Angular 使用 typescript 作为编程语言。typescript 是Javascript 的一个超集&#xff0c;提升了某些功能。本文介绍运行我的第一个angular 程序。 前面部分参考&#xff1a; Angular TypeScript Tutorial in Visual Studio Code 一…

Kafka-Kerberos票据刷新问题

线上kafka使用了 kerberos 认证&#xff0c;每隔24小时&#xff0c;票据过期&#xff0c;无法自动续期&#xff0c;出现消息发送失败问题。 从日志可以发现会有如下报错&#xff1a; 2023-09-14 17:48:47,144 [kafka-kerberos-refresh-thread-kafka/hdp-1HADOOP.COM] [] WARN …

gitee 远程仓库操作基础(二)

(1&#xff09;clone远端仓库,本地建立分支推送 (基于远程仓库版本库 本地建立分支开发新功能) git clone gitgitee.com:xxxxx/alsa_test.git git remote add origin gitgitee.com:xxxxx/alsa_test.git进入clone过后路径代码,查看本地分支,发现该项目远程仓库有很多分支 基于…

Spring Framework 学习笔记5:事务

Spring Framework 学习笔记5&#xff1a;事务 1.快速入门 1.1.准备工作 这里提供一个示例项目 transaction-demo&#xff0c;这个项目包含 Spring 框架、MyBatis 以及 JUnit。 对应的表结构见 bank.sql。 服务层有一个方法可以用于在不同的账户间进行转账&#xff1a; Se…

机器学习之单层神经网络的训练:增量规则(Delta Rule)

文章目录 权重的调整单层神经网络使用delta规则的训练过程 神经网络以权值的形式存储信息,根据给定的信息来修改权值的系统方法称为学习规则。由于训练是神经网络系统地存储信息的唯一途径&#xff0c;因此学习规则是神经网络研究中的一个重要组成部分 权重的调整 &#xff08…

【中秋国庆不断更】OpenHarmony多态样式stateStyles使用场景

Styles和Extend仅仅应用于静态页面的样式复用&#xff0c;stateStyles可以依据组件的内部状态的不同&#xff0c;快速设置不同样式。这就是我们本章要介绍的内容stateStyles&#xff08;又称为&#xff1a;多态样式&#xff09;。 概述 stateStyles是属性方法&#xff0c;可以根…

蓝桥等考Python组别十级003

第一部分&#xff1a;选择题 1、Python L10 &#xff08;15分&#xff09; 已知s Pencil&#xff0c;下列说法正确的是&#xff08; &#xff09;。 s[0]对应的字符是Ps[1]对应的字符是ns[-1]对应的字符是is[3]对应的字符是e 正确答案&#xff1a;A 2、Python L10 &am…