【Vue2.x源码系列05】异步更新及nextTick原理

news2024/11/13 3:46:38

vue 官网中是这样描述 nextTick 的

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

在学习 nextTick 是如何实现之前,我们要先了解下 JavaScript 的执行机制

JavaScript 执行机制

 

浏览器是多线程的,例如GUI渲染线程、JS引擎线程、事件监听线程等。。。

javascript 执行机制就是借用浏览器的多线程机制,再基于 Event Loop 事件循环机制实现的。其实现了单线程异步效果

Event Loop 步骤大致如下:

  1. 浏览器加载页面时,除了开辟堆栈内存外,还会创建两个队列
    • Web API:任务监听队列,监测异步任务是否可以执行
    • Task Queue:任务队列,分为异步宏任务队列和异步微任务队列
  2. 当主线程自上而下执行代码过程中,如果遇到异步代码,则把异步任务放到 Web API 中去监听
    • 浏览器会开辟新的线程去监听是否可以执行
    • 不会阻碍主线程的渲染,它会继续向下执行同步代码
  3. 当异步任务被监测为可以执行了(有了运行结果),也不会立即去执行,而是在 task queue 中放置一个事件,排队等待执行
    • 根据微任务还是宏任务,放在不同的队列中
    • 谁先进来排队的,谁在各自队伍的最前面
  4. 执行栈中的所有同步任务执行完毕,主线程空闲下来,此时会去 task queue 中把正在排队的事件,按照顺序取出来,进入主线程执行
    • 微任务优先级比较高。当执行栈为空时,先去执行微任务队列中的事件,直到微任务队列为空,才会去执行宏任务队列中的事件
  5. 上述过程会不断重复,也就是常说的事件循环(Event Loop)

task 又分为宏任务(macro task)和微任务(micro task)两大类,在浏览器环境中

  1. 常见的 macro task 有 script(整体代码)、setTimeout/setInterval/setImmediateXMLHttpRequest/fetch,DOM事件(如鼠标点击、滚动页面、放大缩小等),渲染事件(解析 DOM、计算布局、绘制)
  2. 常见的 micro task 有 Promise.then/catch/finallyasync/awaitMutationObserver

需要注意的是!!!如果处理微任务的过程中有新的微任务添加进来了,添加的速度一直比执行快,则永远执行微任务

下面的代码永远不会打印宏任务输出

javascript

function macroFn(){
   setTimeout(() => {
     console.log('>>>>MA')
   },0)
}
function microFn(){
    Promise.resolve().then(() => {
        console.log('mi')
        microFn()
    })
}
macroFn()
microFn()

nextTick实现原理

vue2.7 源码中,有一个单独的文件src/core/util/next-tick.js去维护 nextTick,有兴趣的同学可以自行去观看

vue2.7 源码中,nextTick并没有直接使用某个 API ,而是采用了优雅降级的方案去实现异步更新

在内部会尝试使用原生的Promise.then (IE不支持)MutationObserver 和 setImmediate (高版本IE专享),如果执行环境还不支持的话,则会采用 setTimeout(fn, 0)

需要注意的是,我们维护了一个 callbacks,用于存储 nextTick 回调

这样就保证了在同一个 tick 内多次调用 nextTick,只需创建一个异步任务,就可以依次执行 callbacks 中的所有 nextTick 回调。而不是去开启多个异步任务去处理。

javascript

let callbacks = [] // 存储 nextTick 回调
let waiting = false // 防抖

// 按照顺序依次执行 callbacks 中的方法
function flushCallbacks() {
  let cbs = callbacks.slice(0)
  waiting = false
  callbacks = []
  cbs.forEach(cb => cb()) 
}


let timerFunc;
if (Promise) {
    timerFunc = () => {
        Promise.resolve().then(flushCallbacks)
    }
}else if(MutationObserver){
    let observer = new MutationObserver(flushCallbacks); // 这里传入的回调是异步执行的
    let textNode = document.createTextNode(1);
    observer.observe(textNode,{
        characterData:true
    });
    timerFunc = () => {
        textNode.textContent = 2;
    }
}else if(setImmediate){
    timerFunc = () => {
       setImmediate(flushCallbacks);
    }
}else{
    timerFunc = () => {
        setTimeout(flushCallbacks);
     }
}

export function nextTick(cb) {
  callbacks.push(cb) // 维护 nextTick 中的 cakllback 方法
  
  if (!waiting) {
    timerFunc()
    waiting = true
  }
}

异步更新

vue 内部的异步更新渲染也使用了 nextTick

在 Watcher 类的 update 更新方法中,我们调用了 queueWatcher 异步队列更新方法,该方法在 vue2.7源码中的 src/core/util/scheduler.js 文件中维护

javascript

import { queueWatcher } from './scheduler'

class Watcher {
	...
  // 重新渲染
  update() {
    console.log('watcher-update')
    queueWatcher(this) // watcher 异步更新
  }
}

src/core/util/scheduler.js

javascript

import { nextTick } from '../util/next-tick'

/**
 * @name QueueWatcher,内部 watcher 异步更新
 * @decs 把当前的 watcher 暂存起来,在一个tick周期内,不管我们的 update 执行多少次,只会执行一轮刷新操作
 */

let queue = []
let has = {}
let pending = false // 防抖

function flushSchedulerQueue() {
  let flushQueue = queue.slice(0)
  queue = []
  has = {}
  pending = false
  flushQueue.forEach(q => q.run()) // 在刷新的过程中可能还有新的 watcher,重新放到 queue 中
}

// 在一个tick周期内,不管我们的 update 执行多少次,只会执行一轮刷新操作
export function queueWatcher(watcher) {
  const id = watcher.id
  if (!has[id]) {
    queue.push(watcher)
    has[id] = true
    if (!pending) {
      nextTick(flushSchedulerQueue)
      pending = true
    }
  }
}

常见问题

1. nexTick 是异步还是同步?

这个不能一概而论,nextTick 内部既有同步代码又有异步代码。

例如 维护 callbacks 队列是同步任务;执行队列中的方法是异步任务

2. nextTick 回调的执行是微任务还是宏任务?

针对 vue2.7 来说,nextTick并没有直接使用某个 API ,而是采用了优雅降级的方案去实现异步更新。
在内部会尝试使用原生的Promise.then (微任务)MutationObserver (微任务) 和 setImmediate (宏任务),如果执行环境还不支持的话,则会采用 setTimeout (宏任务)

可以理解为 99% 的场景下都是微任务,只有在不支持 Promise 和 MutationObserver API的浏览器中,才会是宏任务,例如 IE9 、IE10

3. 为什么要封装 nextTick?而不是使用某个具体的 API?

优雅降级。尽量使用微任务,尽可能缩短渲染周期

保证统一性。nextTick 可以暴露给用户,保证用户在修改数据之后立即使用这个方法,可以获取更新后的 DOM

javascript

this.name = 'libc'

this.$nextTick(()=>{
  console.log(document.querySelector('.user').innerHTML)
}); 

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

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

相关文章

Kindling the Darkness: A Practical Low-light Image Enhancer论文阅读笔记

这是ACMMM2019的一篇有监督暗图增强的论文,KinD其网络结构如下图所示: 首先是一个分解网络分解出R和L分量,然后有Restoration-Net和Adjustment-Net分别去对R分量和L分量进一步处理,最终将处理好的R分量和L分量融合回去。这倒是很常…

简要介绍 | 航行家的视觉:理解Odometry, Relocalization和SLAM的区别与联系

注1:本文系“简要介绍”系列之一,仅从概念上对计算机视觉和机器人导航中的Odometry、Relocalization和SLAM进行非常简要的介绍,不适合用于深入和详细的了解。 航行家的视觉:理解Odometry, Relocalization和SLAM的区别与联系 Senso…

什么样台灯能保护眼睛?分享眼科医生推荐的台灯好物

很多时候,我们在挑选台灯时只关注了台灯的亮度是否足够,以为只要台灯够亮就不会伤眼睛。而实际上我们大多数视力的下降原因就与光有着非常大的关系,传统的白炽灯、荧光灯等等对我们这一辈造成的伤害就不多说了。如今条件好了,而LE…

高等数学教材啃书汇总难点(一)函数与极限

教材为理工科标配的同济大学第七版,本系列为一轮啃书,将必会的全部重难点悉数总结——尤其是各种晦涩的理论证明部分,考研数学一的选手,想冲击高分的话必须掌握。对于考研证明题部分,熟练掌握定义是必不可少的底层基础…

StoneDB亮相2023数据技术嘉年华:增强AP、升级TP、信创替换,让万千DBA用得更省心,企业用得更省钱

2023 年 4 月 8 日,第十二届『数据技术嘉年华』(DTC 2023) 在北京圆满举办。本届大会以“开源 融合 数智化 —— 引领数据技术发展,释放数据要素价值”为主题。大会汇聚众多优秀厂商、先进技术、卓越产品和优秀案例,来自数据领域的领军人物…

C语言指针进阶-2

本篇文章带 1. 数组传参和指针传参 2. 函数指针 3. 函数指针数组 的相关知识详细讲解! 如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加油,一起奔跑,让我们顶峰相见&…

明明已经安装字体,但IDEA、CLION无法找到思源黑体/Source Hans Sans的问题解决

IDEA、CLION的Jetbrain系列软件不支持非TrueType的中文字体,而Adobe官方给出的字体却不是TrueType的,所以便会导致Jetbrain系软件无法找到已安装的中文字体,因此我们需要安装TrueType的字体 请在以下Github链接中下载: TrueType思…

HTTP——返回结果的HTTP状态码

HTTP 一、状态码告知从服务器端返回的请求结果二、2XX成功1、200 OK2、204 No Content3、206 Partial Content 三、3XX重定向1、301 Moved Permanently2、302 Found3、303 See Other4、304 Not Modified5、307 Temporary Redirect 四、4XX客户端错误1、400 Bad Request2、401 U…

c++11 标准模板(STL)(std::basic_ifstream)(三)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ifstream : public std::basic_istream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istrea…

大模型开发(十五):从0到1构建一个高度自动化的AI项目开发流程(上)

全文共5600余字&#xff0c;预计阅读时间约13~20分钟 | 满满干货(附全部代码)&#xff0c;建议收藏&#xff01; 本文目标&#xff1a;提出一种利用大语言模型(LLMs)加快项目的开发效率的解决思路&#xff0c;本文作为第一部分&#xff0c;主要集中在如何完整的执行引导Chat模…

无涯教程-jQuery - scrollTop( val )方法函数

scrollTop(val)方法用于将滚动顶部偏移量设置为所有匹配元素上的传递值。 scrollTop( val ) - 语法 selector.scrollTop( val ) 这是此方法使用的所有参数的描述- val - 代表所需滚动顶部偏移的正数。 scrollTop( val ) - 示例 以下是一个简单的示例&#xff0c;简单…

DLMS协议开发指引

1 进入DLMS模式 某些电表一开始未进入DLMS传送模式&#xff0c;需要进行切换。按照IEC62056-21协议模式E的规定&#xff0c;先发送以下内容&#xff1a; 典型的串口设置为&#xff1a;波特率300&#xff0c;校验位Even&#xff0c;数据位7。&#xff08;也可校验位无&#xff…

插入排序算法

插入排序 算法说明与代码实现&#xff1a; 以下是使用Go语言实现的插入排序算法示例代码&#xff1a; package mainimport "fmt"func insertionSort(arr []int) {n : len(arr)for i : 1; i < n; i {key : arr[i]j : i - 1for j > 0 && arr[j] > …

CentOS 7虚拟机 虚拟机安装安装增强VBox_GAs_6.1.22失败:modprobe vboxguest failed

我安装的CentOS 在安装增强工具的时候报错: 查阅资料后 &#xff0c;解决方法&#xff1a; 1、更新kernel内核版本&#xff1a; yum update kernel -y //安装kernel-devel和gcc编译工具链yum install -y kernel-devel gcc//更新kernel和kernel-devel到最新版本yum -y upgrade …

Android 卡顿分析与布局优化

一、什么是卡顿&#xff1f;或者说我们怎么感知APP卡顿&#xff1f; 这里面涉及到android UI渲染机制&#xff0c;我们先了解一下android UI是怎么渲染的&#xff0c;android的View到底是如何一步一步显示到屏幕上的&#xff1f; android系统渲染页面流程&#xff1a; 1&…

AcWing111. 畜栏预定

输入样例&#xff1a; 5 1 10 2 4 3 6 5 8 4 7输出样例&#xff1a; 4 1 2 3 2 4 解析&#xff1a; 按照时间区间左端点排序&#xff0c;优先队列&#xff0c;每次弹出能够使用的、最早的畜栏。如果最早的也不能使用&#xff0c;新开一个&#xff0c;否则就放进去。 #includ…

AI绘画教程:为艺术而生的算法,你还在烦恼小红书与公众号的配图吗?(下)

大家好&#xff0c;我是千寻哥&#xff0c;在上一篇给大家分享了我的第一篇AI绘画类教程的上集&#xff1a; AI绘画教程&#xff1a;为艺术而生的算法&#xff0c;你还在烦恼小红书与公众号的配图吗&#xff08;上&#xff09;&#xff1f; 别着急&#xff0c;今天就来完成下半…

CVPR2023新作:源数据集对迁移学习性能的影响以及相应的解决方案

Title: A Data-Based Perspective on Transfer Learning (迁移学习的基于数据的观点) Affiliation: MIT (麻省理工学院) Authors: Saachi Jain, Hadi Salman, Alaa Khaddaj, Eric Wong, Sung Min Park, Aleksander Mądry Keywords: transfer learning, source dataset, dow…

Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类

通常一个对象创建、销毁非常耗时的时候&#xff0c;我们不会频繁的创建和销毁它&#xff0c;而是考虑复用。复用对象的一种做法就是对象池&#xff0c;将创建好的对象放入池中维护起来&#xff0c;下次再用的时候直接拿池中已经创建好的对象继续用&#xff0c;这就是池化的思想…

2、Tomcat介绍(下)

组件分类 在Apache Tomcat中&#xff0c;有几个顶级组件&#xff0c;它们是Tomcat的核心组件&#xff0c;负责整个服务器的运行和管理。这些顶级组件包括&#xff1a; Server(服务器)&#xff1a;Tomcat的server.xml配置文件中的<Server>元素代表整个Tomcat服务器实例。每…