宏任务,微任务,事件循环event loop、setTimeout,setInterva、nextTick与process.nextTickl区别【示例讲解】

52 篇文章 14 订阅
11 篇文章 0 订阅

目录

js单线程

竞态:DOM操作冲突

禁止跨线程访问DOM:webworker线程不能直接操作DOM

事件回调简单

同步:JS

宏任务

在主线程上排队执行的任务,顺序执行

由宿主(浏览器、Node)发起

宏任务macrotask: 定时事件(setTimeout,setInterval),Ajax,DOM/用户交互事件,HTML解析, I/O 

优先级:js>setImmediate(Node环境)>setTimeout

微任务

不进入主线程、而进入"微任务列表"的任务

由 JS 自身发起

微任务microtask(异步):Promise 、async/await、alert、MutationObserver,process.nextTick,queueMicrotask

优先级:process.nextTick>其他微任务>alert>Promise=async/await

queueMicrotask(JS)

MutationObserver( 浏览器端 )

async、await事件轮询执行时机

async隐式返回Promise,会产生一个微任务await xx;后的代码在微任务时执行

nextTick(Vue):DOM更新后执行回调函数

实现nectTick

promise>MutationObserver>setImmediate>setTimeout

process.nextTick( Node 端 )

事件轮询机制

0.在执行宏任务过程中,遇到微任务,依次加入微任务队列

1.当前宏任务执行完后,会判断微任务列表中是否有任务

1.5.如果有,会把所有微任务放到主线程中并执行

因此微任务优先级高于宏任务,用于处理更紧急的工作

1.5.如果没有,就继续执行下一个宏任务

重复上面loop

setTimeout(delay=0)=setImmediate:下个Event Loop执行

event loop 与 浏览器更新渲染时机

宏任务 → 微任务 → 渲染更新

大量宏任务时,可以将DOM->微任务

拓展:任务的数据结构

少量定时任务:小顶堆(按时间顺序,堆化时间长)

多个定时任务:双向环形链表,时间指针,多层时间轮

普通任务:队列(先进先出)

js单线程

竞态:DOM操作冲突

竞态条件(race condition)和死锁(deadlock):一个删除DOM,另外一个访问DOM

禁止跨线程访问DOM:webworker线程不能直接操作DOM

安全性:JSt主要是在浏览器端执行,多线程的设计可能引发安全隐患,例如跨线程访问共享数据容易导致数据不一致非法访问

事件回调简单

单线程按顺序执行JS时,遇到事件触发时,将事件回调函数添加到队列中。

为了解决高耗时操作就带来的进程阻塞问题,

Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

同步:JS

日常的 DOM 操作通常是同步的,并不直接分为异步模式(宏任务或微任务):

修改元素属性:如更改 classNameid 或 style 属性

document.body.style.backgroundColor = "red"; // 修改背景颜色

宏任务

在主线程上排队执行的任务,顺序执行

由宿主(浏览器、Node)发起

宏任务macrotask: 定时事件()setTimeout,setInterval定时事件(),Ajax,DOM/用户交互事件,HTML解析, I/O 

优先级:js>setImmediate(Node环境)>setTimeout

浏览器对页面的渲染和重绘优化异步任务以提高性能:

宏任务:用户交互事件(如点击事件、滚动等)

微任务:视图更新请求;微任务队列会在当前宏任务(例如一个点击事件的处理函数)完成后,且在下一个宏任务开始前执行执行所有的视图更新

微任务

不进入主线程、而进入"微任务列表"的任务

由 JS 自身发起

微任务microtask(异步):Promise 、async/await、alert、MutationObserver,process.nextTick,queueMicrotask

优先级:process.nextTick>其他微任务>alert>Promise=async/await

看eventloop代码说输出的实例

queueMicrotask(JS)

MutationObserver( 浏览器端 )

MutationObserver 是浏览器提供的一个用于监测 DOM 变化的接口。它允许开发者在 DOM 树发生变化时进行异步处理。MutationObserver 可以观察到节点的增删改等操作

// 创建一个 MutationObserver 实例,指定回调函数
const observer = new MutationObserver((mutations, observer) => {
  mutations.forEach((mutation) => {
    // 处理每个变化(mutation)
    console.log(mutation.type); // 变化的类型(attributes、childList、characterData)
    console.log(mutation.target); // 受影响的节点
  });
});

// 配置观察选项
const config = {
  attributes: true, // 观察属性的变化
  childList: true, // 观察子节点的变化
  subtree: true, // 观察所有后代节点的变化
  characterData: true, // 观察字符数据的变化
  // 其他配置项...
};

// 开始观察目标节点
observer.observe(targetNode, config);

// 在不需要观察时,可以调用 disconnect 方法停止观察
// observer.disconnect();

async、await事件轮询执行时机

async隐式返回Promise,会产生一个微任务
await xx;后的代码在微任务时执行


//1.script start(同步)
console.log("script start");

async function async1() {
  await async2(); // await 隐式返回promise
  console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}

async function async2() {
  console.log("async2 end"); // 这里是同步代码
}
//2.async2 end(同步)
//微任务队列:[async1 end]
async1();
//宏任务队列:[setTimeout],setTimeOut进入下一loop
setTimeout(function() {
  console.log("setTimeout");
}, 0);
//3.Promise(同步)
//宏任务队列:[setTimeout]
//微任务队列:[async1 end,promise1]
new Promise(resolve => {
  console.log("Promise"); // 这里是同步代码
  resolve();
})
  .then(function() {
    console.log("promise1");
  })
  .then(function() {
    console.log("promise2");
  }); 
//4.script end(同步)
console.log("script end");
//当前loop的宏任务(都是同步代码)都执行完毕
//执行所有微任务[async1 end,promise1]
//执行promise1完后碰到了promise2,加入微任务队列,接着执行
//当前所有微任务都执行完毕,开始执行宏任务队列[setTimeout]

// 打印结果:  script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

nextTick(Vue):DOM更新后执行回调函数

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

  • vue2:promise>MutationObserver>setImmediate>setTimeout

执行优先级:微任务(MutationObserver>promise)>宏任务(setImmediate>setTimeout)

setImmediate 在当前事件循环的结束后立即执行

setTimeout 则是在指定的延迟时间后才执行。

因此,setImmediate 的执行时机比 setTimeout 更,更接近于微任务的执行时机,响应更快

MutationObserver开销高于promise

  • vue3:promise(vue3基于 Proxy 响应式,而Proxy基于promise

实现nectTick

promise>MutationObserver>setImmediate>setTimeout
  1. Promise:如果浏览器支持PromisenextTick会优先使用Promise.then来创建微任务,以确保回调函数在下一个微任务队列中执行。

  2. MutationObserver:否则。MutationObserver变动观察器)是一种Web API,它允许开发者监视DOM树的变化并在这些变化发生时执行回调函数允许监视DOM树的变化,因此它也可以用于异步任务的调度。

  3. setImmediate:否则。setImmediate是一种宏任务,通常比setTimeout执行得更早,因此它用于创建宏任务级别的异步任务。

  4. setTimeout:如果以上方法都不可用,nextTick会回退到使用setTimeout来创建异步任务。setTimeout是一种宏任务,但是优先级较低,可能在其他异步任务之后执行。

// 定义nextTick的回调队列
let callbacks = [];

// 批量执行nextTick的回调队列
function flushCallbacks() {
  callbacks.forEach((cb) => cb());
  callbacks = [];
  pending = false;
}

//定义异步方法,优先使用微任务实现
let timerFunc;

// 优先使用promise 微任务
if (Promise) {
  timerFunc = function () {
    return Promise.resolve().then(flushCallbacks);
  };
  // 如不支持promise,再使用MutationObserver 微任务
} else if (MutationObserver) {
  timerFunc = function () {
    const textNode = document.createTextNode('1');
    const observer = new MutationObserver(() => {
      flushCallbacks();
      observer.disconnect();
    });
    const observe = observer.observe(textNode, { characterData: true });
    textNode.textContent = '2';
  };
  // 微任务不支持,再使用宏任务实现
} else if (setImmediate) {
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else {
  timerFunc = function () {
    setTimeout(flushCallbacks);
  };
}

// 定义nextTick方法
export function nextTick(cb) {
  callbacks.push(cb);
  if (!pending) {
    pending = true;
    timerFunc();
  }
}

process.nextTick( Node 端 )

console.log("start");
//定时进入下一loop,宏任务队列:[timeout]
setTimeout(() => {
  console.log("timeout");
}, 0);
//微任务队列:[promise]
Promise.resolve().then(() => {
  console.log("promise");
});
//process.nextTick在微任务队首
//微任务队列:[nextTick,promise]
process.nextTick(() => {
  console.log("nextTick");
  Promise.resolve().then(() => {
    console.log("promise1");
  });
});
console.log("end");
// 执行结果 start end nextTick  promise promise1 timeout 

事件轮询机制

0.在执行宏任务过程,遇到微任务,依次加入微任务队列

1.当前宏任务执行完后,会判断微任务列表中是否有任务

1.5.如果有,会把所有微任务放到主线程中并执行

因此微任务优先级高于宏任务,用于处理更紧急的工作

1.5.如果没有,就继续执行下一个宏任务

重复上面loop

setTimeout(delay=0)=setImmediate:下个Event Loop执行

//宏任务队列:[]
//微任务队列:[promise0]
Promise.resolve()
  .then(function() {
    console.log("promise0");
  })
  .then(function() {
    console.log("promise5");
  });
//定时的setTimeout(delay=0)=setImmediate:下个Event Loop执行
//宏任务队列:[timer1]
//微任务队列:[promise0]
setTimeout(() => {
  console.log("timer1");
  
  Promise.resolve().then(function() {
    console.log("promise2");
  });
  Promise.resolve().then(function() {
    console.log("promise4");
  });
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0]
setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(function() {
    console.log("promise3");
  });
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0,promise1]
Promise.resolve().then(function() {
  console.log("promise1");
});
//执行start
console.log("start");
//执行当前所有微任务队列:[promise0,promise1]
//执行promise0时将promise5放入了微任务队列:[promise1,promise5]
//接着执行微任务队列:输出promise1,promise5
//当微任务队列为空,开始执行宏任务队列[timer1,timer2]队首的timer1
//执行timer1时碰到了微任务promise2,放进微任务队列[promise2]
//宏任务timer1执行完了,开始执行所有当前所有微任务:[promise2]
//执行promise2完碰到微任务promise4,放进微任务队列:[promise4]
//当前微任务队列不为空,接着执行promise4
//微任务队列为空,接着执行宏任务队列队首[timer2]
//执行timer2时碰到了微任务promise3,放进微任务队列[promise3]
//宏任务timer2执行完了,开始执行所有当前所有微任务:[promise3]


// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3

event loop 与 浏览器更新渲染时机

宏任务 → 微任务 → 渲染更新

浏览器更新渲染会在event loop中的 宏任务 和 微任务 完成后进行,即宏任务 → 微任务 → 渲染更新(先宏任务 再微任务,然后再渲染更新)

大量宏任务时,可以将DOM->微任务

宏任务队列中,如果有大量任务等待执行时,将dom的变动作为微任务,能更快的将变化呈现给用户,这样就可以在这一次的事件轮询中更新dom

拓展:任务的数据结构

少量定时任务:小顶堆(按时间顺序,堆化时间长)

多个定时任务:双向环形链表,时间指针,多层时间轮

普通任务:队列(先进先出)

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值