一、浏览器JS异步执行的原理

一般常说js是一门单线程语言,那为什么可以异步执行且不发生阻塞呢?

  • 常说的JS是单线程语言,是因为执行JS的引擎是单线程的,而浏览器本身是多线程的
  • 浏览器主要含有:
    • js 执行线程
    • 定时器线程
    • http 请求线程
    • 事件触发线程
    • GUI 线程等
  • 异步请求的真正执行者是浏览器的其他线程
  • js 引擎只是执行了异步操作成功了之后的回调函数

二、事件循环机制

1 - 执行栈和任务队列

(1)执行栈是什么

  • 用于按执行顺序存放同步代码
  • 按序执行,执行完毕后弹出执行栈
  • 如果在执行过程中遇到异步操作,就交给其他线程处理

(2)任务队列

  • 用于按序排放异步操作执行结束后的回调函数
  • 任务队列中的函数等待执行栈执行结束后取出执行

2 - 事件循环的本质

  • 基于事件驱动模式
  • 至少包含了一个事件循环来判断当前的任务队列是否有新的任务
  • 通过不断的循环取出异步回调进行执行

3 - 宏任务和微任务

(1)宏任务的分类

  • 渲染事件
  • 用户交互事件
  • SetTimeout、setInterval
  • 网络请求、文件读写等

    有明确异步操作的任务,需要其他的异步线程支持

(2)微任务的分类

  • promise.then promise.catch
  • process.nextTick

    没有明确的异步任务需要执行,只有回调不需要其他异步线程的支持

(3)执行顺序

  • 在同步代码执行结束后
  • 先执行微任务队列中的微任务
  • 再执行宏任务队列中的宏任务

(4)案例

  1. console.log('同步代码1');
  2. setTimeout(() => {
  3. console.log('setTimeout')
  4. }, 0)
  5. new Promise((resolve) => {
  6. console.log('同步代码2')
  7. resolve()
  8. }).then(() => {
  9. console.log('promise.then')
  10. })
  11. console.log('同步代码3');
  12. // 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"
  1. setTimeout(() => {
  2. console.log('setTimeout start');
  3. new Promise((resolve) => {
  4. console.log('promise1 start');
  5. resolve();
  6. }).then(() => {
  7. console.log('promise1 end');
  8. })
  9. console.log('setTimeout end');
  10. }, 0);
  11. function promise1() {
  12. return new Promise((resolve) => {
  13. console.log('promise2');
  14. resolve();
  15. })
  16. }
  17. async function async1() {
  18. console.log('async1 start');
  19. await promise1();
  20. console.log('async1 end');
  21. }
  22. async1();
  23. console.log('script end');
  24. // 输出结果
  25. async1 start
  26. promise2
  27. script end
  28. async1 end
  29. setTimeout start
  30. promise1 start
  31. setTimeout end
  32. promise1 end

4 - 定时器误差

(1)执行顺序

  • 遇到一个定时器请求,开启定时器线程去计时
  • 计时结束后将回调函数放入到任务队列(宏任务)
  • 等待同步任务执行 waiting…… ,在同步任务执行结束后可能还有微任务
  • 同步任务执行结束后取出任务队列中的回调

(2)误差大小

  • 定时器的误差值取决于同步任务的执行时间 + 微任务的执行时间
  • 同步任务执行时间越长,定时器的误差越大

三、脑图总结

事件循环机制.png