您现在的位置是:群英 > 开发技术 > web开发
事件循环及宏任务微任务在JS的工作原理
Admin发表于 2022-05-17 17:53:19787 次浏览
这篇文章主要给大家介绍“事件循环及宏任务微任务在JS的工作原理”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“事件循环及宏任务微任务在JS的工作原理”文章能对大家有所帮助。

本质上来说,javascript是同步的、阻塞的、单线程语言,不管是在浏览器中还是nodejs环境下。浏览器在执行js代码和渲染dom节点都是在同一个线程中,执行js代码就无法渲染dom,渲染dom的时候就无法执行js代码。如果按照这种同步方式执行,页面的渲染将会出现白屏甚至是报错,特别是遇到一些耗时比较长的网络请求或者js代码,因此在实际开发中一般是通过异步的方式解决。

什么是异步?js是一步一步执行代码的,遇到alert这种阻塞代码时,js将会停止往下执行直到阻塞代码执行完毕。异步就是将函数放在单独的异步队列中,不会产生阻塞,js可以继续往下执行,等到同步代码执行完毕后再执行异步队列中的函数。因此,js会先执行完同步代码,才会执行异步代码。

异步也有细分,每一个异步函数可当作是一个待执行的任务,可分为宏任务和微任务。本文重点介绍浏览器的事件循环,nodejs环境下的事件循环放在下一篇中继续探讨。

宏任务

宏任务,也可简单的说成是任务,在下一轮dom渲染之后执行。常见的宏任务有:

settimeout:设置一个定时器,该定时器会在设置的延迟时间到期后执行一个函数或者指定的代码块。值得注意的是,settimeout不一定会在延迟时间到达后就立即执行函数,而是会判断执行队列中是否还有函数没有处理,如果没有了并且栈为空,settimeout才会在延迟时间到达后执行函数。

// settimeout 延迟执行不等于到期时立即执行
let now = new date().getseconds();
settimeout(() => {
    console.log('this is settimeout 0');
}, 0);
settimeout(() => {
    console.log('this is settimeout 200');
}, 200);
while(true) {
    if (new date().getseconds() - now >= 2) {
        console.log('break out while loop');
        break;
    }
}

运行结果

break out while loop
this is settimeout 0
this is settimeout 200

先执行同步代码,再执行异步。settimeout(() => {}, 0)表示0毫秒后立即执行函数,但是当前执行队列中还有未处理完的while循环,因此需要等到while循环执行完毕后,才会根据延迟到期时间执行函数。

setinterval:设置定时器,表示在固定的时间间隔内,重复执行某一函数或者特定的代码块。注意使用setinterval有最小延迟时间限制以及确保执行时间要小于间隔时间,如果执行时间无法确定,则应采用递归调用settimeout的方式代替。

网络请求:只要是指xmlhttprequest等网络请求

微任务

微任务,在下一轮dom渲染之前执行,微任务比宏任务更早执。常见的微任务有:

promise:表示一个异步操作最终的结果和返回值,可能会失败,也可能成功。异步函数在执行时,什么时候返回结果是不可预料的,promise把异步操作的返回值和函数关联起来,保证在异步执行结束后会执行对应的函数,并通过函数返回操作值。这种效果就类似于把异步代码“同步执行”。

queuemicrotask:将函数添加到微任务队

console.log('start');
// 微任务队列
promise.resolve().then(() => {
    console.log('promise then');
});
queuemicrotask(() => {
    console.log('queuemicrotask');
});
console.log('end');

运行结果

start
end
promise then
queuemicrotask

事件循环

因为有异步操作的存在,所以出现了事件循环,如果都是同步操作,一行一行执行代码,事件循环也就失去了用武之地。在了解事件循环前,还需要补充js的执行过程:

js在执行代码时,遇到函数就会将其添加到调用栈中,每一帧都会存储当前函数的参数和局部变量,当一个函数执行完毕,则会从调用栈中弹出,直到栈被清空,那么程序也就执行完毕。在执行的过程中,需要的引用数据都是从堆中获取。

在实际开发中,往往是同步代码和异步代码都有。在js执行时,还是从第一行代码开始执行,遇到函数就将其添加到栈中,然后执行同步操作;如果遇到异步函数,则根据其类型,宏任务就添加到宏任务队列,微任务添加到微任务队列。直到同步代码执行完毕,则开始执行异步操作。

异步操作后于同步操作,异步操作内部也是分先后顺序的。总的来说:

  • 微任务先于宏任务执行
  • 微任务与微任务之间根据先后顺序执行,宏任务与宏任务之间根据延迟时间顺序执行
  • 微任务在下一轮dom渲染前执行,宏任务在下一轮dom渲染之后执行
  • 每个任务的执行都是一次出栈操作,直到栈被清空

微任务比宏任务先执行

console.log('start');
// 宏任务队列
settimeout(() => {
    console.log('settimeout');
});
// 微任务队列
promise.resolve().then(() => {
    console.log('promise then');
});
console.log('end');

执行结果

start
end
promise then
settimeout

微任务在下一轮dom渲染前执行,宏任务在之后执行

let div = document.createelement('div');
div.innerhtml = 'hello world';
document.body.appendchild(div);
let divlist = document.getelementbytagname('div');
console.log('同步任务 length ---', list.length);
console.log('start');
settimeout(() => {
    console.log('settimeout length ---', list.length);
    alert('宏任务 settimeout 阻塞'); // 使用alert阻塞js执行
});
promise.resolve().then(() => {
    console.log('promise then length ---', list.length);
    alert('微任务 promise then 阻塞);
});
console.log('end');

事件循环

event loop会持续监听是否有异步操作,如果有则添加到对应的队列中,等待执行。例如在宏任务中添加微任务,或者在微任务中添加宏任务,当前任务执行完后,可能还会有新的任务添加到事件循环中。

宏任务与微任务

微任务中创建宏任务

    new promise((resolve) => {
      console.log('promise 1');
      settimeout(() => {
        console.log('settimeout 1');
      }, 500);
      resolve();
    }).then(() => {
      console.log('promise then');
      settimeout(() => {
        console.log('settimeout 2');
      }, 0);
    });
    new promise((resolve) => {
      console.log('promise 2');
      resolve();
    })

运行结果

promise 1
promise 2
promise then
settimeout 2
settimeout 1

解析

js执行代码,遇到两个promise,则分别添加到微任务队列,同步代码执行完毕。

在微任务队列中根据先进先出,第一个promise先执行,遇到settimeout,则添加到宏任务队列,resolve()返回执行结果并执行then,事件循环将其继续添加到微任务队列;第一个promise执行完毕,执行第二个promise。

继续执行微任务队列,直到清空队列。遇到settimeout,并将其添加到宏任务队列

宏任务队列现在有两个任务待执行,由于第二个settimeout的延迟事件更小,则优先执行第二个;如果相等,则按照顺序执行。

继续执行宏任务队列,直到清空队列。

宏任务中创建微任务

    settimeout(() => {
      console.log('settimeout 1');
      new promise((resolve) => {
        console.log('promise 1');
        resolve();
      }).then(() => {
        console.log('promise then');
      })
    }, 500);
    settimeout(() => {
      console.log('settimeout 2');
      new promise((resolve) => {
        console.log('promise 2');
        resolve();
      })
    }, 0);

运行结果

settimeout 2
promise 2
settimeout 1
promise 1
promise then

解析

js执行代码,遇到两个settimeout,将其添加到宏任务队列,同步代码执行完毕

先检查微任务队列中是否有待处理的,刚开始肯定没有,因此直接执行宏任务队列中的任务。第二个为零延迟,需要优先执行。遇到promise,将其添加到微任务队列。第一个宏任务执行完毕

在执行第二个宏任务时,微任务队列中已经存在待处理的,因此需要先执行微任务。

微任务执行完毕,并且延迟时间到期,第一个settimeout开始执行。遇到promise,将其添加到微任务队列中

执行微任务队列中的promise,执行完毕后遇到then,则将其继续添加到微任务队列

直到所有微任务执行完毕

宏任务中创建宏任务

    settimeout(() => {
      console.log('settimeout 1');
      settimeout(() => {
        console.log('settimeout 2');
      }, 500);
      settimeout(() => {
        console.log('settimeout 3');
      }, 500);
      settimeout(() => {
        console.log('settimeout 4');
      }, 100);
    }, 0);

运行结果

settimeout 1
settimeout 4
settimeout 2
settimeout 3

解析

宏任务中创建宏任务,执行顺序一般来说是按照先后顺序的。对于settimeout来说,延迟时间相同,则按照先后顺序执行;延迟时间不同,则按照延迟时间的大小先后顺序执行

微任务中创建微任务

    new promise((resolve) => {
      console.log('promise 1');
      new promise((resolve) => {
        console.log('promise 2');
        resolve();
      });
      new promise((resolve) => {
        console.log('promise 3');
        resolve();
      })
      resolve();
    })

运行结果

promise 1
promise 2
promise 3

解析

微任务中创建微任务,执行顺序一般来说是按照先后顺序执行的。

总结

  • 同步代码直接执行,异步代码添加到宏任务队列或者微任务队列
  • 微任务在下一轮dom渲染前执行,宏任务在下一轮dom渲染之后执行
  • 事件循环持续监听
  • 如果存在异步操作,需要将关联代码放在异步函数中执行
  • 如果代码层次比较复杂,同步、异步代码混杂,一定要理清代码的执行顺序。避免因为异步,导致代码出现难以察觉的bug



通过以上内容的阐述,相信大家对“事件循环及宏任务微任务在JS的工作原理”已经有了进一步的了解,更多相关的问题,欢迎关注群英网络或到群英官网咨询客服。

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

相关信息推荐
2021-11-22 17:53:55 
摘要:这篇文章我们来了解python中如何使用正则表达式的内容,下文将介绍正则表达式的元字符、方法以及检索和替换的内容,有需要的朋友可以参考,接下来就跟随小编一起学习一下吧!
2022-07-04 17:38:01 
摘要:这篇文章主要为大家介绍了Go语言基础关于go接口的用法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,在日升职加薪
2022-05-26 17:53:43 
摘要:go语言int64转string的方法:首先创建一个go示例文件;然后输入代码“i := int64(123)”;最后通过“s := strconv.FormatInt(i, 10)”方法将int64转string即可。
云活动
推荐内容
热门关键词
热门信息
群英网络助力开启安全的云计算之旅
立即注册,领取新人大礼包
  • 联系我们
  • 24小时售后:4006784567
  • 24小时TEL :0668-2555666
  • 售前咨询TEL:400-678-4567

  • 官方微信

    官方微信
Copyright  ©  QY  Network  Company  Ltd. All  Rights  Reserved. 2003-2019  群英网络  版权所有   茂名市群英网络有限公司
增值电信经营许可证 : B1.B2-20140078   粤ICP备09006778号
免费拨打  400-678-4567
免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
微信公众号
返回顶部
返回顶部 返回顶部