nextTick 的使用和原理(面试题)

news2024/9/30 9:30:33

答题思路:

  1. nextTick 是做什么的?
  2. 为什么需要它?
  3. 开发时什么时候使用?
  4. 介绍一下如何使用nextTick
  5. 原理解读,结合异步更新和nextTick生效方式

1. nextTick是做什么的?

nextTick是等待下一次DOM更新刷新的工具方法。

<script>
import { nextTick } from 'vue'

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    async increment() {
      this.count++

      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0

      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
  }
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

2. 为什么需要它?

Vue 有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick。

3. 开发时什么时候使用?

4. 介绍一下如何使用nextTick

function nextTick(callback?: () => void): Promise<void>

所以我们只需要在传入的回调函数中访问最新DOM状态即可,或者我们可以await nextTick()方法返回的Promise之后做这件事。

5. 原理解读,结合异步更新和nextTick生效方式

在Vue内部,nextTick之所以能够让我们看到DOM更新后的结果,是因为我们传入的callback会被添加到列队刷新函数(flushSchedulerQueue)的后面,这样等队列内部的更新函数都执行完毕,所有DOM操作也就结束了,callback自然能够获取到最新的DOM值。

将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务;

nextTick 提供了四种异步方法 Promise.then、MutationObserver、setImmediate、setTimeout(fn,0)

  • vue2

出于兼容性考虑,依次判断浏览器是否支持,选择使用对应api
在这里插入图片描述
优先选择微任务,如果微任务都不支持,则选择宏任务

  • vue3

抛弃了兼容性,直接使用Promise,来实现nextTick


由nextTick的源码可以看出,nextTick本质就是创建了一个微任务(不考虑setTimeout),将其回调推入微任务队列。vue中一个事件循环中的所有dom更新操作也是一个微任务,两者属于同一优先级,执行先后只于入队的先后有关,换句话说,如果你先写了nextTick,再写赋值语句(在此之前没有触发dom更新的操作),那在nextTick中获取的可就不是更新后的dom了

<template>
  <div class="demo">
    <p class="name">{{ name }}</p>
    <button @click="modify">修改</button>
  </div>
</template>
<script lang="ts" setup>
const name = ref("111");

const modify = () => {
  name.value = "222"; // 关键的赋值语句,如果注释掉,结果就大不一样了
  nextTick(() => {
    const text = document.querySelector<HTMLElement>(".name").innerText;
    console.log(text);
  });
  name.value = "333";
};
</script>

如上代码,如果注释掉name.value = “2222”,虽然nextTick语句下面也有赋值操作:name.value = “3333”;,但由于nextTick先进入微任务队列,所以回调先于dom更新执行,所以是获取的dom仍旧是旧的更新前的dom
在这里插入图片描述

  • 源码补充
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

//  上面三行与核心代码关系不大,了解即可
//  noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错
//  handleError 错误处理函数
//  isIE, isIOS, isNative 环境判断函数,
//  isNative 判断某个属性或方法是否原生支持,如果不支持或通过第三方实现支持都会返回 false


export let isUsingMicroTask = false     // 标记 nextTick 最终是否以微任务执行

const callbacks = []     // 存放调用 nextTick 时传入的回调函数
let pending = false     // 标记是否已经向任务队列中添加了一个任务,如果已经添加了就不能再添加了
    // 当向任务队列中添加了任务时,将 pending 置为 true,当任务被执行时将 pending 置为 false
    // 


// 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数
// 回调的 this 自动绑定到调用它的实例上
export function nextTick(cb?: Function, ctx?: Object) {
    let _resolve
    // 将传入的回调函数存放到数组中,后面会遍历执行其中的回调
    callbacks.push(() => {
        if (cb) {   // 对传入的回调进行 try catch 错误捕获
            try {
                cb.call(ctx)
            } catch (e) {    // 进行统一的错误处理
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    
    // 如果当前没有在 pending 的回调,
    // 就执行 timeFunc 函数选择当前环境优先支持的异步方法
    if (!pending) {
        pending = true
        timerFunc()
    }
    
    // 如果没有传入回调,并且当前环境支持 promise,就返回一个 promise
    // 在返回的这个 promise.then 中 DOM 已经更新好了,
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}


// 判断当前环境优先支持的异步方法,优先选择微任务
// 优先级:Promise---> MutationObserver---> setImmediate---> setTimeout
// setTimeout 可能产生一个 4ms 的延迟,而 setImmediate 会在主线程执行完后立刻执行
// setImmediate 在 IE10 和 node 中支持

// 当在同一轮事件循环中多次调用 nextTick 时 ,timerFunc 只会执行一次

let timerFunc   
// 判断当前环境是否原生支持 promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {  // 支持 promise
    const p = Promise.resolve()
    timerFunc = () => {
       // 用 promise.then 把 flushCallbacks 函数包裹成一个异步微任务
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
        // 这里的 setTimeout 是用来强制刷新微任务队列的
        // 因为在 ios 下 promise.then 后面没有宏任务的话,微任务队列不会刷新
    }
    // 标记当前 nextTick 使用的微任务
    isUsingMicroTask = true
    
    
    // 如果不支持 promise,就判断是否支持 MutationObserver
    // 不是IE环境,并且原生支持 MutationObserver,那也是一个微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
    let counter = 1
    // new 一个 MutationObserver 类
    const observer = new MutationObserver(flushCallbacks) 
    // 创建一个文本节点
    const textNode = document.createTextNode(String(counter))   
    // 监听这个文本节点,当数据发生变化就执行 flushCallbacks 
    observer.observe(textNode, { characterData: true })
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter)  // 数据更新
    }
    isUsingMicroTask = true    // 标记当前 nextTick 使用的微任务
    
    
    // 判断当前环境是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => { setImmediate(flushCallbacks)  }
} else {

    // 以上三种都不支持就选择 setTimeout
    timerFunc = () => { setTimeout(flushCallbacks, 0) }
}


// 如果多次调用 nextTick,会依次执行上面的方法,将 nextTick 的回调放在 callbacks 数组中
// 最后通过 flushCallbacks 函数遍历 callbacks 数组的拷贝并执行其中的回调
function flushCallbacks() {
    pending = false    
    const copies = callbacks.slice(0)    // 拷贝一份 callbacks
    callbacks.length = 0    // 清空 callbacks
    for (let i = 0; i < copies.length; i++) {    // 遍历执行传入的回调
        copies[i]()
    }
}

// 为什么要拷贝一份 callbacks

// 用 callbacks.slice(0) 将 callbacks 拷贝出来一份,
// 是因为考虑到在 nextTick 回调中可能还会调用 nextTick 的情况,
// 如果在 nextTick 回调中又调用了一次 nextTick,则又会向 callbacks 中添加回调,
// 而 nextTick 回调中的 nextTick 应该放在下一轮执行,
// 否则就可能出现一直循环的情况,
// 所以需要将 callbacks 复制一份出来然后清空,再遍历备份列表执行回调

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

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

相关文章

电子电器架构——怎样在请求/响应 ID确定的情况下修改发送FD 的CAN ID?

我是穿拖鞋的汉子,魔都中一个坚持长期主义的工程师! 老规矩,分享一段喜欢的文字,避免成为高知识低文化的人: 能不传话,最好不要传话;能不套话,最好不要套话;能不涉入“背后的批评”,最好不要涉入。让自己像沙滩,多大的浪来了,也是轻抚着沙滩,一波波地退去。而不要…

Ubuntu 快速切换到指定目录

现有以下场景&#xff0c;假设我在本地有/home/pc/Downloads/temp/Project 目录&#xff0c;我想快速在终端进入Project目录&#xff0c;需要怎么操作呢 文件管理器 由于我知道这个目录在哪个位置&#xff0c;那我就可以打开文件管理器&#xff0c;进入到这个目录&#xff0c…

关于数据治理ChatGPT是如何回答的?

这两天你的朋友圈是不是被火爆全网的ChatGPT霸屏了&#xff1f;你是不是已经迫不及待感受过ChatGPT带来的惊喜&#xff1f;那你知道ChatGPT是什么吗&#xff1f;面对掀起的一波话题热潮&#xff0c;好奇使然&#xff0c;小编去特别关注了一下最近火热的ChatGPT&#xff0c;看看…

基于Spring cloud搭建oauth2

1&#xff0c;OAuth2.0简介 OAuth&#xff08;开发授权&#xff09;是一个开放标准&#xff0c;允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息&#xff0c;而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。 OAuth2.0是OAuth的延续&#xf…

预告| 亮点抢先看!第四届OpenI/O启智开发者大会主论坛24日启幕!

2023年2月24日至25日&#xff0c;第四届OpenI/O启智开发者大会将在深圳隆重举行。“算网筑基、开源启智、AI赋能”作为今年大会的主题&#xff0c;吸引了全球业界关注的目光。大会集结中国算力网资源基座、开源社区治理及AI开源生态建设、国家级开放创新应用平台、NLP大模型等前…

2023年云计算的发展趋势如何?还值得学习就业吗?

一、2023年云计算的发展将迎来新篇章 随着政策的正式放开&#xff0c;2023年的经济开始慢慢复苏&#xff0c;云计算在疫情期间支撑了复工复产&#xff0c;那么在今年对于云计算发展的限制将进一步的放开。Gartner的数据显示&#xff0c;到2023年&#xff0c;全球公共云支出将达…

MybatisPlus------条件构造器Wrapper以及QueryWrapper用法(七)

MybatisPlus------条件构造器Wapper&#xff08;七&#xff09; Wrapper:条件构造器抽象类&#xff0c;最顶端父类 AbstarctWrapper&#xff1a;用于查询条件封装&#xff0c;生成sql的where条件。 QueryWrapper&#xff1a;查询条件封装&#xff08;可以用于查询、删除&#x…

Java必备小知识点1

Java程序类型: Applications和AppletApplications:是指在计算机操作系统中运行的程序。是完整的程序&#xff0c;能独立运行。被编译后&#xff0c;用普通的Java解释器就可以使其边解释边执行。必定含有一个main方法&#xff0c;程序执行时&#xff0c;首先寻找main方法&#x…

IDEA中如何配置SpringBoot项目多实例不同端口运行

1 问题场景 我们在进行新项目开发的时候&#xff0c; 可能做完一个新的模块功能并自测通过之后&#xff0c; 我们希望测试人员能帮我跑一些单元测试用例来进行测试验证&#xff0c; 但是我们又需要在此基础上技术开发新的功能&#xff0c; 这是我们就需要在我们的开发PC上同时…

Prometheus监控案例-kube-state-metrics

kube-state-metrics组件介绍 kube-stste-metrics项目地址&#xff1a;https://github.com/kubernetes/kube-state-metrics kube-stste-metrics是一个简单的组件&#xff0c;通过监听API Server生成有关资源对象的状态指标&#xff08;例如Deployment、Pod、Node等&#xff09…

HiEV洞察 | 卖一台亏半台,激光雷达第一股禾赛隐忧仍在

作者 | 感知君Alex 编辑 | 王博2月9日晚&#xff0c;禾赛在万众瞩目下登陆纳斯达克&#xff0c;发行价19美元每股&#xff0c;首日涨超11%&#xff0c;市值超过Luminar&#xff0c;登顶全球市值最高的激光雷达公司。 随后两个交易日&#xff0c;其股价均有不同程度的涨幅&#…

08- 汽车产品聚类分析综合项目 (机器学习聚类算法) (项目八)

项目难点 主要通过聚类算法 kmeans 进行调整 . 需要找出分为几类时模型参数最佳 . (n_clusters)找出性价比较高的车 获取训练数据: train_X data.drop([car_ID,CarName],axis 1)计算模型的得分和误差: kmeans.inertia_ # inertia簇内误差平方和 from sklearn.cluster i…

【深度学习/机器学习】为什么要归一化?归一化方法详解

【深度学习/机器学习】为什么要归一化&#xff1f;归一化方法详解 文章目录1. 介绍1.1 什么是归一化1.2 归一化的好处2. 归一化方法2.1 最大最小标准化&#xff08;Min-Max Normalization&#xff09;2.2 Z-score标准化方法2.3 非线性归一化2.4 L范数归一化方法&#xff08;最典…

宝塔搭建实战人才求职管理系统admin前端vue源码(二)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 上一期给大家分享骑士cms后台端在宝塔的搭建部署方式&#xff0c;这套系统是前后端分离的架构&#xff0c;前端是用vue2开发的&#xff0c;还需要在本地打包手动发布上宝塔&#xff0c;所以本期给大家分享&#x…

智能笔式万用表简单体验加拆解 - VC6012C - 智能电笔

简而言之&#xff0c;能用&#xff0c;甚至还挺好用的&#xff0c;机身大小参考上面的示意图&#xff0c;跟比较粗的记号笔差不多。单纯想买个万用表的话&#xff0c;如果不追求这种精简的外形&#xff0c;同价位有其他功能更强的选项。其实就是个能自动切换档位的智能万用表加…

山东大学软件学院面向对象简答题整理【个人向】

面向对象简答题整理【个人向】 0.试用面向对象语言简述改写和重定义的异同&#xff0c;以及方法绑定时的差别 改写是子类的方法和父类的方法具有相同的方法名和类型签名重定义是子类的方法和父类的方法方法名相同但类型签名不同在方法绑定时&#xff0c;改写是动态绑定&#…

kettle开发-Day38-其实chatGPT一直在身边

前言&#xff1a;最近chatGPT火出圈&#xff0c;其实不是chatGPT多智能&#xff0c;只是它用了一种新的交互方式来组织我们现有的知识&#xff0c;然后通过“高智商”的表达来使我们惊艳。但是目前或者未来的人工智能缺少创造力&#xff0c;他们只会整合信息目的是提高我们的效…

力扣sql简单篇练习(十八)

力扣sql简单篇练习(十八) 1 报告的记录 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT extra report_reason,count(distinct post_id) report_count FROM Actions WHERE action_dateDATE_SUB(2019-07-05,interval 1 day) AND extra IS NOT N…

突破监管“困局”,ScanV为您提供重保安全监测保障!

三月重保即将开始&#xff0c;重保期间是重要时间区间、重要基础设施和重要业务系统安全保障的“三重”考验期。 作为基于实战的网络安全提供商&#xff0c;知道创宇ScanV为您提供三月重保期间免费安全监测保障&#xff0c;并专门针对监管客户重保时期需求提供实战化监测保障方…

GGX发布全新路线图,揭示具备 Layer0 特性且可编程的跨链基建生态

据彭博社报道&#xff0c;具备跨链通信且可编程的 Layer0 基础设施协议 Golden Gate (GGX) 已进行了 两年的线下开发&#xff0c;于近日公开发布了最新的路线图&#xff0c;该路线图不仅显示了该生态在过去两年的发展历程&#xff0c;也披露了 2023 年即将实现的重要里程碑。 G…