• 宏任务,微任务,事件循环event loop与promise、setTimeout、async、nextTick【超详细示例讲解】


    目录

    js单线程

    竞态:DOM操作冲突

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

    事件回调简单

    宏任务

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

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

    宏任务macrotask: setTimeout,setInterval定时事件,Ajax,DOM事件,script 脚本的执行、 I/O 操作、UI 渲染等。

    优先级: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

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

    事件回调简单

    同步模型:JavaScript的单线程模型使得事件驱动和回调机制相对容易实现。当有事件触发时,JavaScript将该事件添加到事件队列中,然后按照顺序依次执行队列中的事件回调函数。这种机制使得处理异步操作、定时器和用户交互事件等变得简单。

    但是一些高耗时操作就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

    宏任务

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

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

    宏任务macrotask: setTimeout,setInterval定时事件,Ajax,DOM事件,script 脚本的执行、 I/O 操作、UI 渲等。

    优先级: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 可以观察到节点的增删改等操作

    1. // 创建一个 MutationObserver 实例,指定回调函数
    2. const observer = new MutationObserver((mutations, observer) => {
    3. mutations.forEach((mutation) => {
    4. // 处理每个变化(mutation)
    5. console.log(mutation.type); // 变化的类型(attributes、childList、characterData)
    6. console.log(mutation.target); // 受影响的节点
    7. });
    8. });
    9. // 配置观察选项
    10. const config = {
    11. attributes: true, // 观察属性的变化
    12. childList: true, // 观察子节点的变化
    13. subtree: true, // 观察所有后代节点的变化
    14. characterData: true, // 观察字符数据的变化
    15. // 其他配置项...
    16. };
    17. // 开始观察目标节点
    18. observer.observe(targetNode, config);
    19. // 在不需要观察时,可以调用 disconnect 方法停止观察
    20. // observer.disconnect();

    async、await事件轮询执行时机

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

    1. //1.script start(同步)
    2. console.log("script start");
    3. async function async1() {
    4. await async2(); // await 隐式返回promise
    5. console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
    6. }
    7. async function async2() {
    8. console.log("async2 end"); // 这里是同步代码
    9. }
    10. //2.async2 end(同步)
    11. //微任务队列:[async1 end]
    12. async1();
    13. //宏任务队列:[setTimeout],setTimeOut进入下一loop
    14. setTimeout(function() {
    15. console.log("setTimeout");
    16. }, 0);
    17. //3.Promise(同步)
    18. //宏任务队列:[setTimeout]
    19. //微任务队列:[async1 end,promise1]
    20. new Promise(resolve => {
    21. console.log("Promise"); // 这里是同步代码
    22. resolve();
    23. })
    24. .then(function() {
    25. console.log("promise1");
    26. })
    27. .then(function() {
    28. console.log("promise2");
    29. });
    30. //4.script end(同步)
    31. console.log("script end");
    32. //当前loop的宏任务(都是同步代码)都执行完毕
    33. //执行所有微任务[async1 end,promise1]
    34. //执行promise1完后碰到了promise2,加入微任务队列,接着执行
    35. //当前所有微任务都执行完毕,开始执行宏任务队列[setTimeout]
    36. // 打印结果: script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

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

    实现nectTick

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

    2. MutationObserver:如果浏览器不支持PromisenextTick会检查是否支持MutationObserverMutationObserver变动观察器)是一种Web API,它允许开发者监视DOM树的变化并在这些变化发生时执行回调函数允许监视DOM树的变化,因此它也可以用于异步任务的调度。nextTick会尝试使用MutationObserver来创建微任务。

    3. setImmediate:如果浏览器既不支持Promise也不支持MutationObservernextTick会检查是否支持setImmediatesetImmediate是一种宏任务,通常比setTimeout执行得更早,因此它用于创建宏任务级别的异步任务。

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

    1. // 定义nextTick的回调队列
    2. let callbacks = [];
    3. // 批量执行nextTick的回调队列
    4. function flushCallbacks() {
    5. callbacks.forEach((cb) => cb());
    6. callbacks = [];
    7. pending = false;
    8. }
    9. //定义异步方法,优先使用微任务实现
    10. let timerFunc;
    11. // 优先使用promise 微任务
    12. if (Promise) {
    13. timerFunc = function () {
    14. return Promise.resolve().then(flushCallbacks);
    15. };
    16. // 如不支持promise,再使用MutationObserver 微任务
    17. } else if (MutationObserver) {
    18. timerFunc = function () {
    19. const textNode = document.createTextNode('1');
    20. const observer = new MutationObserver(() => {
    21. flushCallbacks();
    22. observer.disconnect();
    23. });
    24. const observe = observer.observe(textNode, { characterData: true });
    25. textNode.textContent = '2';
    26. };
    27. // 微任务不支持,再使用宏任务实现
    28. } else if (setImmediate) {
    29. timerFunc = function () {
    30. setImmediate(flushCallbacks);
    31. };
    32. } else {
    33. timerFunc = function () {
    34. setTimeout(flushCallbacks);
    35. };
    36. }
    37. // 定义nextTick方法
    38. export function nextTick(cb) {
    39. callbacks.push(cb);
    40. if (!pending) {
    41. pending = true;
    42. timerFunc();
    43. }
    44. }

    process.nextTick( Node 端 )

    1. console.log("start");
    2. //定时进入下一loop,宏任务队列:[timeout]
    3. setTimeout(() => {
    4. console.log("timeout");
    5. }, 0);
    6. //微任务队列:[promise]
    7. Promise.resolve().then(() => {
    8. console.log("promise");
    9. });
    10. //process.nextTick在微任务队首
    11. //微任务队列:[nextTick,promise]
    12. process.nextTick(() => {
    13. console.log("nextTick");
    14. Promise.resolve().then(() => {
    15. console.log("promise1");
    16. });
    17. });
    18. console.log("end");
    19. // 执行结果 start end nextTick promise promise1 timeout

    事件轮询机制

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

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

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

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

    重复上面loop

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

    1. //宏任务队列:[]
    2. //微任务队列:[promise0]
    3. Promise.resolve()
    4. .then(function() {
    5. console.log("promise0");
    6. })
    7. .then(function() {
    8. console.log("promise5");
    9. });
    10. //定时的setTimeout(delay=0)=setImmediate:下个Event Loop执行
    11. //宏任务队列:[timer1]
    12. //微任务队列:[promise0]
    13. setTimeout(() => {
    14. console.log("timer1");
    15. Promise.resolve().then(function() {
    16. console.log("promise2");
    17. });
    18. Promise.resolve().then(function() {
    19. console.log("promise4");
    20. });
    21. }, 0);
    22. //宏任务队列:[timer1,timer2]
    23. //微任务队列:[promise0]
    24. setTimeout(() => {
    25. console.log("timer2");
    26. Promise.resolve().then(function() {
    27. console.log("promise3");
    28. });
    29. }, 0);
    30. //宏任务队列:[timer1,timer2]
    31. //微任务队列:[promise0,promise1]
    32. Promise.resolve().then(function() {
    33. console.log("promise1");
    34. });
    35. //执行start
    36. console.log("start");
    37. //执行当前所有微任务队列:[promise0,promise1]
    38. //执行promise0时将promise5放入了微任务队列:[promise1,promise5]
    39. //接着执行微任务队列:输出promise1,promise5
    40. //当微任务队列为空,开始执行宏任务队列[timer1,timer2]队首的timer1
    41. //执行timer1时碰到了微任务promise2,放进微任务队列[promise2]
    42. //宏任务timer1执行完了,开始执行所有当前所有微任务:[promise2]
    43. //执行promise2完碰到微任务promise4,放进微任务队列:[promise4]
    44. //当前微任务队列不为空,接着执行promise4
    45. //微任务队列为空,接着执行宏任务队列队首[timer2]
    46. //执行timer2时碰到了微任务promise3,放进微任务队列[promise3]
    47. //宏任务timer2执行完了,开始执行所有当前所有微任务:[promise3]
    48. // 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3

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

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

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

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

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

  • 相关阅读:
    数据链路层:封装成帧
    关于请求和重定向的路径问题
    Linux第70步_新字符设备驱动的一般模板
    130. 被围绕的区域
    【数据结构与算法】用队列实现栈&&用栈实现队列&&设计循环队列
    华为云云服务器云耀L实例评测 | 智能不卡顿:如何实现流畅的业务运行
    【读点论文】FMViT: A multiple-frequency mixing Vision Transformer-期待源码
    ASEMI代理艾赛斯IXFK32N100P,车规级MOS管IXFK32N100P
    猿创征文|Java计算【生日工具类】看这篇就够了
    三相Vienna整流器电流畸变的抑制方法
  • 原文地址:https://blog.csdn.net/qq_28838891/article/details/133053470