Vue2.0 —— Vue.nextTick(this.$nextTick)源码探秘

news/2024/5/6 0:55:42/文章来源:https://blog.csdn.net/LizequaNNN/article/details/126957312

Vue2.0 —— Vue.nextTick(this.$nextTick)源码探秘

《工欲善其事,必先利其器》

一、知识储备

在学习这个 API 之前,我们需要进行一定量的知识储备,并且是从最基础的开始:

  1. nextTick,译为:下一个刻度,可理解为下一个事件,下一个要去做的事情;
  2. 浏览器的进程和线程,进程包含着线程,需理解多进程的概念;
  3. 了解 JavaScript 的事件循环机制,包括宏任务和微任务的区别。

浏览器工作原理与实践
(截图来自极客时间网站)

首先说明,这里不是广告,也不是盗图。只是这位老师的课程讲的实在是特别好!强烈建议还没学习的小伙伴可以去学习这门课程,我希望看到的是大家一起进步,而不是得过且过。

  • 现代的浏览器分为多个模块的进程,它们之间互不干扰又部分通信共享,这种底层的技术称为 IPC (Inter Process Communication) ,译为:进程间通信,属于半双工通信,例如我们现实生活中的对讲机。
  • 现代浏览器属于多进程架构,分别有主进程、网络进程、渲染进程、GPU 进程和插件进程。其中,渲染进程运行在沙箱模式下,即:一个 Tabs 就代表一个渲染进程。我们熟知的 JavaScript 线程就运行在这个进程之中。
  • JavaScript 线程又将执行任务分为宏任务和微任务。在 JS 引擎工作的过程中,会产生一个 执行栈,里面用于执行宏任务;如果在宏任务执行的过程中遇见微任务,JS 引擎会将微任务提炼到 任务队列 中,当执行栈栈顶的宏任务执行完之后,在 GPU 渲染之前,执行任务队列中属于该宏任务的微任务。如此循环以往,称之为 事件循环机制
  • 宏任务有:主代码块、setTimeoutsetIntervalsetImmidiate以及 I/O流requestAnimationFrame
  • 微任务有:PromiseObject.observeMutationObserver 以及 process.nextTick(node)。

好了,下面开始进入正题,话不多说,上号!

上号

二、为什么会有这个 API?

由官方的解释引入:

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

在之前我写过一篇文章 —— 《Vue2.0 —— 关于虚拟节点和 Diff 算法的浅析》 一文中提到,开发者们为了提升 SPA 的性能可谓是绞尽脑汁,不仅应用了虚拟节点的技术,还实现了 Diff 算法,目的就是提升更为优异的性能。最后我们得出的结论是:Vue 只会在执行完 Diff 算法 之后渲染一次 DOM。

但你有没有想过,这与我们上面做的知识储备是否背道而驰?明明每次执行完宏任务,就会进行一次 GPU 渲染,那为什么官网还倡导我们在数据修改之后立即使用这个方法去获取更新后的 DOM 呢?

那我们不妨大胆猜想,Vue 如果没有指定立即刷新视图(sync 关键字),那么他的 render 调用视图更新方法,极有可能就是异步的,而且是属于 微任务(事实上也的确如此,后面的源码会分析)。Fine,事情开始变的有趣了起来。

搞笑

三、使用方式

  • Vue.nextTick
vm.msg = "Hello";
// DOM 还没更新
/*** {Function} callback* {Object} context
*/
Vue.nextTick(function() {// DOM 更新了
})

这个方法属于全局应用,值得注意的是,如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不是原生支持 Promise (IE:你们都看我干嘛),你得自行 polyfill。

  • vm.$set
new Vue({// ...methods: {// ...example: function () {// 修改数据this.message = 'changed'// DOM 还没有更新/*** {Function} callback* {Object} context*/this.$nextTick(function () {// DOM 现在更新了// `this` 绑定到当前实例this.doSomethingElse()})}}
})

这个方法是应用在组件内的方式,与上面的全局方法在本质上实现并无二致,后者仅仅是前者的一个别名。

四、源码探秘

接下来是官方的原话:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

皇天不负有心人,我们上面的猜想被验证了。无独有偶,在 JavaScript 的发展历程中,鉴于 JS 单线程引擎的工作原理,我们的前辈也想要在浏览器主代码执行完之后、浏览器渲染之前,可以做一些操作。因此 JavaScript 的 事件循环机制 以及 宏任务微任务 的概念就诞生了。

而我们的 Vue 框架,为此也向程序猿们提供了本文这个 API (Vue.nextTick)。并且,这个方法最终也成为了 Vue 渲染视图的主要手段,造成了异步更新 DOM 的现象,间接的提升了 Vue 框架的性能。

  • 第一,我们修改响应式数据之后,触发 dep.notify

触发更新

  • 第二,Dep 利用观察者模式通知已经收集好的 Watcher 进行视图更新;

执行更新

  • 第三,每个 Watcher 将自己推入到任务队列里面;

推入队列

  • 第四,ShedulerWatcher 进行识别判断,如果属于同一 Watcher 则会被忽略;

推入队列具体实现

  • 第五,重点来了:Vue.config.async 默认是 true,如果没有设置,那么 Vue 会利用 nextTick 方法,将 flushSchedulerQueue() 处理流函数,提取到异步任务队列之中。如果设置了,那么就是立即同步执行,这就是典型的,“异步渲染机制”。

flushScheduleQueue函数
flushScheduleQueue() 处理流函数用于执行 Watcherrun 回调方法,以及部分生命周期的更新,重置 Schedule 实例等。

  • 第六,才是我们今天的主题,Vue.nextTick
// 执行微任务标识
export let isUsingMicroTask = false
// 储存任务数组
const callbacks: Array<Function> = []
// 执行标识,默认为false
let pending = false
// 执行任务数组里面的回调函数
function flushCallbacks() {pending = falseconst copies = callbacks.slice(0)callbacks.length = 0for (let i = 0; i < copies.length; i++) {copies[i]()}
}
// 声明回调函数集
let timerFunc
// Vue 默认使用 Promise 作为异步任务,上面分析过浏览器的差异,所以下面要判断其他方法
if (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()// 允许使用 Promise 浏览器情况下的回调函数赋值timerFunc = () => {p.then(flushCallbacks)if (isIOS) setTimeout(noop)}isUsingMicroTask = true
} 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})// 允许使用 MO 浏览器情况下的回调函数赋值timerFunc = () => {counter = (counter + 1) % 2textNode.data = String(counter)}isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {// 允许使用 setImmediate 浏览器情况下的回调函数赋值timerFunc = () => {setImmediate(flushCallbacks)}
} else {// Fallback to setTimeout.// 摆烂情况下的赋值timerFunc = () => {setTimeout(flushCallbacks, 0)}
}export function nextTick(): Promise<void>
export function nextTick<T>(this: T, cb: (this: T, ...args: any[]) => any): void
export function nextTick<T>(cb: (this: T, ...args: any[]) => any, ctx: T): void
/*** @internal*/
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {let _resolve// 这里就没啥了,回调函数加入数组callbacks.push(() => {if (cb) {try {cb.call(ctx)} catch (e: any) {handleError(e, ctx, 'nextTick')}} else if (_resolve) {_resolve(ctx)}})if (!pending) {pending = true// 执行回调函数集timerFunc()}// $flow-disable-lineif (!cb && typeof Promise !== 'undefined') {return new Promise(resolve => {_resolve = resolve})}
}

捋一下吧:Vue 内部默认使用异步渲染机制,最终调用 nextTick 方法,这个 API 的作用就是,先是把所有的回调函数加入“回调函数集合”数组,再把不同浏览器可用的微任务做了一个判断、适配和循环赋值(flushCallbacks),最终添加完了之后,执行回调函数集合。

当然了,这个 API 也是对外暴露的,任何开发者都可以使用。

五、用例测试

测试1
输出:40,这个应该是没有什么问题对吧,我们再看一组。

测试2
这里即使 agenextTick 函数后面,但你前面已经执行修改 gender 触发了收集依赖,所以,微任务就会等主代码执行完之后,再执行回调,所以这里打印出来的,依然是更新之后的 DOM ,输出: 40。

测试3
同样,这里也会输出:40;即便你多次执行同一个 Watcher 的更新,Vue 会对其进行去重操作,并不会修改一次属性就更新一次视图,这部分是为了性能做的优化。

测试4
这个输出:20。因为 nextTick 的回调在异步渲染的回调之前执行,所以获取不到更新后的 DOM。

最后,感谢你的阅读,愿你的未来一片光明~

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

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

相关文章

激光雷达物体检测(二):点视图检测算法

综述&#xff1a; 1.点视图&#xff1a; PointNet/PointNet&#xff0c;Point-RCNN&#xff0c;3D SSD 2.俯视图&#xff1a; VoxelNet&#xff0c;SECOND&#xff0c;PIXOR&#xff0c;AFDet 3.前视图: LaserNet&#xff0c;RangeDet 4.多视图融合&#xff1a; 俯视图…

谷粒商城 高级篇 (十九) --------- 消息队列

目录一、概述二、应用三、RabbitMQ 概念四、安装 RabbitMQ五、RabbitMQ 运行机制Exchange 类型六、RabbitMQ 整合七、RabbitMQ 消息确认机制1. ConfirmCallback2. ReturnCallback3. Ack 消息确认机制一、概述 大多应用中&#xff0c;可通过消息服务中间件来提升系统异步通信、…

python实现图像添加噪声、噪声处理、滤波器代码实现

目录 加载图像添加噪声 图像傅里叶变换和反变换并可视化 图像处理---高通滤波、低通滤波、带通滤波 低通滤波器---Butterworth低通滤波器、理想低通滤波器、高斯低通滤波器 加载图像添加噪声 高斯噪声是指它的概率密度函数服从高斯分布&#xff08;即正态分布&#xff09;…

Linux设置开机自启动Java程序--三种方式

Linux设置开机自启动Java脚本程序 缘起 公司内部的服务器中有个SpringCloud项目需要运行&#xff0c;之前都是通过nohup java-jar .. &的命令来执行的&#xff0c;但是这个cloud项目服务太多&#xff0c;手动启动太麻烦而且容易出错&#xff0c;干脆写个执行java的脚本好…

C++内存管理(每日更新)

文章目录0 概述0.1 四个层面的基本用法1 Primitives1.1 new expression1.2 delete expression1.3 调用构造函数与析构函数1.4 array new & array delete1.4.1 array new0 概述 C应用程序malloc非常重要 可以看出&#xff0c;C内存管理主要是有四个层面 0.1 四个层面的基本…

NFT重构票务系统

什么是NFT&#xff1f; NFT是运行在区块链上的一种不可分割的凭证&#xff08;Non-Fungible Token&#xff09;&#xff0c;或者称为非同质化代币。NFT目前主要用在数字艺术品的铸造、拍卖、流转&#xff0c;因为一个NFT能唯一地确定它的所有者&#xff0c;并可在链上跟踪每一…

自然语言语义分析研究进展_笔记

自然语言语义分析研究进展_笔记 词语语义分析&#xff1a;确定词语意义&#xff0c;衡量两个词之间的语义相似度或相关度; 句子语义分析&#xff1a;研究包含句义分析和句义相似度分析两方面; 文本语义分析&#xff1a;识别文本的意义、主题、类别等语义信息的过程&#xff…

使用@JsonFormat并进一步了解:格式化java.util.Date对象

Java 8 Spring Boot 2.7.3 jackson 2.13.3 -- ben发布于博客园 0、前言 开发过程中遇到问题: 前端调用接口得到的时间对象(java.util.Date)总是存在这样那样的问题。 调查后发现,可以使用 @JsonFormat注解(来自jackson依赖包)解决相关问题。 ben发布于博客园 新建spring …

区块链分叉带来的安全挑战

区块链分叉分为软分叉和硬分叉。本文主要探讨的是硬分叉&#xff0c;一种不支持向后兼容的软件升级方式。硬分叉是共识的分裂或者改变&#xff0c;共识就是区块链系统中各节点达成数据一致性的算法&#xff0c;正常情况下每个节点需要运行相同规则的算法&#xff0c;例如比特币…

计算机毕业设计之java+javaweb的影院管理系统-电影院管理系统

计算机毕业设计之javajavaweb的影院管理系统-电影院管理系统 项目介绍 影院的需求和管理上的不断提升,影院管理的潜力将无限扩大,影院管理系统在业界被广泛关注,本网站及对此进行总体分析,将影院信息管理的发展提供参考。影院管理系统对影院发展有着明显的带动效应,尤其对当地影…

【ManageEngine】OpManager 2022用户体验报告

关于SoftwareViews SoftwareReviews是Info-Tech Research Group的一个部门&#xff0c;是一家世界级的技术研究和咨询公司&#xff0c;拥有超过20年的基于研究的IT建议和技术实施。 SoftwareViews务实的工具和详细的客户洞察力帮助软件购买者在技术决策中取得最大成功。 Sof…

Java并发编程解析 | 基于JDK源码解析Java领域中ReentrantLock锁的设计思想与实现原理 (一)

苍穹之边,浩瀚之挚,眰恦之美; 悟心悟性,善始善终,惟善惟道! —— 朝槿《朝槿兮年说》写在开头在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。主要原因是,对于多线程实现实现并发,一直以…

全能赛道、热门方向、稀缺数据,“嬴彻-清华AIR杯”自动驾驶技术大赛火热开赛中

如果你关注自动驾驶,那有这样一个算法大赛,值得参与。由卡车自动驾驶领导者嬴彻科技与清华大学智能产业研究院(AIR)精心打造的“嬴彻-清华AIR杯”自动驾驶技术挑战赛正火热开赛中。这是国内首个同时覆盖干线物流和城市道路双赛道的大赛。决策规划是自动驾驶当下的热门方向和技术…

go语言的基本数据类型

基本数据类型中的常量已经介绍了 var const iota 。此处要对字符串特别说明&#xff0c;字符串也会被认为是基本数据类型&#xff0c;字符串实际在底层原理上与复合类型的数据非常相似。同事go语言支持八进制&#xff0c;6进制&#xff0c;科学计数法。空指针的值是nil。 整…

百度地图API

一、百度地图API接入 1、搜索百度地图开发平台 2、注册百度账号 3、登陆并申请成为开发者 4、在百度地图开发平台的首页选择控制台&#xff0c;在控制台中创建应用 创建好应用以后就能在控制台我的应用中看到这个应用&#xff0c;其中最重要的是AK&#xff0c;这是百度地图…

从0-1,如何用低代码搭建管理系统

关键字&#xff1a;功能模块、流程中心、OA 前言&#xff1a;对于搭建系统&#xff0c;字眼上都知道是怎么回事&#xff0c;但要怎么搭建到最后怎么呈现一个投入运作的系统&#xff0c;估计就很少人知道了。当然作为专业的程序员肯定知道怎么操作&#xff0c;但是不是必须要专业…

IDEA集成Git

介绍 参考视频教程: https://www.bilibili.com/video/BV1vy4y1s7k6?p27 1 配置 Git 忽略文件 1.1为什么要配置 问题 1:为什么要忽略他们&#xff1f; 答&#xff1a;与项目的实际功能无关&#xff0c;不参与服务器上部署运行。把它们忽略掉能够屏蔽 IDE 工具之 间的差异。…

湖仓一体电商项目(十八):业务实现之编写写入DWD层业务代码

文章目录 业务实现之编写写入DWD层业务代码 一、代码编写 二、​​​​​​​​​​​​​​创建Iceberg-DWD层表 1、在Hive中添加Iceberg表格式需要的包 2、创建Iceberg表 三、代码测试 1、在Kafka中创建对应的topic 2、将代码中消费Kafka数据改成从头开始消费 3、执…

【展馆攻略】展馆室内精准定位导航服务,便捷无忧,所见必达!

近年来&#xff0c;室内定位导航服务被各大商场、园区、景区等场所广泛使用&#xff0c;逐渐融入到人们日常生活中。室内地图从传统的平面信息图发展到智能化3D可视化交互展示&#xff0c;实现了室内综合场景的精准定位导航。 在面对室内或者建筑物遮挡区域&#xff0c;室内导航…

浏览器如何渲染页面?

DOM 浏览器渲染页面的过程就像是盖房子&#xff0c;一般先请求服务器得到HTML文件&#xff0c;HTML文件就相当于网页的框架结构&#xff0c;不过一开始浏览器得到的是显示字节内容的HTML文件&#xff0c;必须要内化为自己看的懂的语言才行&#xff0c;于是就把字节转化为字符&…