likes
comments
collection
share

通过Vue源码分析$nextTick其实可能是宏任务也可能是微任务

作者站长头像
站长
· 阅读数 29

一、前言:

用Vue开发的小伙伴都知道nextTick主要是用来处理异步的,前端生涯早期我一直以为nextTick是微任务,甚至看了很多面经也是这样说。但其实并不是的,学习Vue的底层源码后发现nextTick其实是采用了采用优雅降级,根据不同浏览器支持程度不同而采用不同的方案去实现异步方案,其中的方案有的是宏任务也有的是微任务

二、nextTick前置知识 - js事件循环

JavaScript执行是单线程的,所以JavaScript的事件循环(Event Loop)是其异步编程模型的核心机制,它允许非阻塞的执行环境,让程序能够处理并发任务,如用户输入、网络请求、定时器等,而不会阻碍主线程。以下是JavaScript事件循环的关键组成部分和工作流程:

1. 执行栈

  • JavaScript是单线程的,这意味着同一时间只能执行一个任务。
  • 执行栈是一个数据结构,用于存储当前正在执行的函数调用信息。每当执行一个函数,它就被推入栈顶;当函数执行结束,它就被弹出栈。

2. 任务队列

  • 当异步任务完成后,它们的回调函数并不会立即执行,而是被放置到与之相关的任务队列中。

  • 有两种主要的任务队列:宏任务队列和微任务队列;宏任务在DOM渲染之后执行,微任务则是DOM渲染之前执行。

    • 宏任务包括:setTimeoutsetInterval、I/O、UI渲染等。
    • 微任务包括:Promise的回调(.then())、MutationObserverprocess.nextTick等。

3. 细说事件循环机制

  1. 初始化:首先,执行全局脚本代码,这些代码被看作是第一个宏任务。
  2. 执行栈:执行栈顶部的任务,直到执行栈为空或遇到异步操作。
  3. 检查微任务:在当前宏任务执行完后,检查微任务队列。如果有微任务,全部执行它们,直到微任务队列为空。
  4. 渲染(可选) :在某些环境下,此时浏览器有机会进行UI渲染。
  5. 执行宏任务:从宏任务队列中取出下一个任务,推入执行栈执行。
  6. 重复:重复步骤3到5,直到宏任务和微任务队列都为空,然后事件循环结束。

4. 注意

  • 微任务总是在当前宏任务结束后,下一个宏任务开始前执行。
  • 因为微任务队列的执行优先级高于宏任务,所以在处理大量微任务时要小心,以免造成页面卡顿。
  • 新的异步操作产生的回调,会根据其类型被添加到相应的任务队列中。

三、看看nextTick的源码

思路:采用优雅降级,根据当前环境支持什么则确定调用哪个,按顺序分别有:Promise.thenMutationObserversetImmediatesetTimeout. 分别对应下面14、20、32和37行。

其中setImmediatesetTimeout就是属于宏任务,所以如果当前环境不支持Promise.thenMutationObserver的话nextTick就是宏任务。

let callbacks = [];
let pending = false;
function flushCallbacks() {
  pending = false;
  // 循环依次去执行回调
  for (let i = 0; i < callbacks.length; i++) {
    callbacks[i]();
  }
}

//定义异步方法并采用优雅降级支持什么api就用什么去实现异步
let timerFunc; 
if (typeof Promise !== "undefined") {
  // 如果支持promise
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== "undefined") {
  // 如果支持 MutationObserver
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== "undefined") {
  // 判断是否支持setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // 最后降级采用定时器setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  callbacks.push(cb);
  // 多次调用nextTick,也只执行一次异步,等异步队列清空之后再变为false
  if (!pending) {
    pending = true;
    timerFunc();
  }
}

四、小结:

其实说nextTick就是微任务其实没啥问题,因为基本当前环境不会不支持Promise.thenMutationObserver,但万一不支持呢?所以说是微任务还是不够严谨。

转载自:https://juejin.cn/post/7366256490230693900
评论
请登录