前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript EventLoop

JavaScript EventLoop

作者头像
老猫-Leo
发布2023-12-11 20:55:07
1750
发布2023-12-11 20:55:07
举报
文章被收录于专栏:前端大全

EventLoop 即事件循环机制,是指浏览器或 Node 的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

JavaScript 运行机制

  • 所有同步任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
  • 主线程之外,还存在任务队列(Task Queue)。只要异步任务有了运行结果,就在任务队列之中放置一个事件
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。如果有那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的第三步
  • 一个事件循环中有一个或者是多个任务队列

总结:调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了。 这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。 每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取~执行的操作。

EventLoop 事件循环

介绍

主线程从“任务队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:

  • JavaScript 中有两种异步任务:宏任务(MacroTask)微任务(MicroTask)
  • 主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查『微任务』队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有『微任务』。
  • 然后再进入下一个循环去任务队列中取下一个任务执行。

MacroTask 宏任务

  • script 全部代码、setTimeoutsetIntervalsetImmediate(浏览器暂时不支持,只有 IE10 支持,具体可见 MDN。)、I/OUI Rendering

MicroTask 微任务

  • Process.nextTick(Node独有)、PromiseObject.observe(废弃)MutationObserver(具体使用方式查看这里)

同步任务和异步任务

  JavaScript 单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行。异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中,等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

事件循环的进程模型

  • 选择当前要执行的任务队列,选择任务队列中最先进入的任务,如果任务队列为空即 null,则执行跳转到微任务的执行步骤。
  • 将事件循环中的任务设置为当前选择任务
  • 执行任务
  • 将事件循环中当前运行任务设置为 null
  • 将已经运行完成的任务从任务队列中删除
  • Microtasks 检查步骤,进入微任务检查点。
    • 设置微任务检查点标志为 true。
    • 当事件循环微任务执行队列不为空时:选择一个最先进入的微任务队列的微任务,将事件循环的微任务设置为当前选择的微任务
    • 运行微任务
    • 将已经执行完成的微任务设置为 null
    • 移除微任务队列中的当前运行完成的微任务
    • 清理 IndexDB 事务
    • 设置进入微任务检查点的标志为 false。
  • 更新界面渲染
  • 返回第一步

总结

  • 执行栈在执行完 同步任务 后,查看 执行栈 是否为空,如果为空,就会去检查 微任务队列 是否为空,如果为空的话,就执行 宏任务,否则就一次性执行完 所有微任务
  • 每次单个 宏任务 执行完毕后,检查 微任务队列 是否为空,如果不为空的话,会按照先入先出的规则全部执行完 微任务 后,设置 微任务队列 为 null,然后再执行宏任务,如此循环。

举个栗子

代码

代码语言:javascript
复制
console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

console.log('script end');

执行结果

代码语言:javascript
复制
script start
script end
promise1
promise2
setTimeout

执行步骤

  • 第一次执行:执行同步代码,将宏任务和微任务划分到各自队列中。
  • 第二次执行:执行宏任务后,检测到 微任务队列 中不为空,执行 Promise1,执行完成 Promise1 后,调用 Promise2.then,放入 微任务队列 中,再执行 Promise2.then
  • 第三次执行:当 微任务队列 中为空时,执行 宏任务,执行 setTimeout callback,打印日志。
  • 第四次执行:清空任务队列和调用栈

图例

代码语言:javascript
复制
setTimeout(function () {
  console.log('timer1');
  Promise.resolve()
  .then(function () {
    console.log('promise1');
  });
});

setTimeout(function () {
  console.log('timer2');
  Promise.resolve()
  .then(function () {
    console.log('promise2');
  });
});

再举个栗子

代码语言:javascript
复制
// 例子
console.log('script start');

async function async1() {
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2 end');
}

async1();

setTimeout(function () {
  console.log('setTimeout');
}, 0);

new Promise((resolve) => {
  console.log('Promise');
  resolve();
})
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

console.log('script end');

// 结果
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout


// 只需将 async1 的执行理解为
function async1() {
  return async2().then(() => {
    console.log('async1 end');
  });
}

Sorry, your browser does not support the video tag.

执行步骤如上所示

  • 开始执行
    • 首先我们执行同步代码,先打印 script start
    • 再打印 async2 end,将 async2.then 放入微任务队列中。
    • 继续执行,将 setTimeout 放入宏任务队列中。
    • 再打印 Promise,将 Promise.then 放入微任务队列中。
    • 最后打印 script end
  • 执行完成后,检查微任务队列不为空,按照先进先出原则继续执行。
    • 执行 async2.then 打印 async1 end
    • 执行 Promise.then 打印 promise1,并将 promise1.then 放入微任务队列中。
  • 此时检查微任务队列继续不为空
    • 执行 promise1.then 打印 promise2
  • 最后执行宏任务队列中的任务
    • 执行 setTimeout,延迟时间到后,将其回调函数放入任务队列中。
  • 执行回调函数
    • 打印 setTimeout

习题

题目

代码语言:javascript
复制
console.log('script start');

async function async1() {
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2 end');
}

async1();

setTimeout(function () {
  console.log('setTimeout1');
}, 100);

setTimeout(function () {
  console.log('setTimeout2');
}, 10);

setTimeout(function () {
  console.log('setTimeout3');
}, 0);

setTimeout(function () {
  console.log('setTimeout4');
}, 0);

new Promise((resolve) => {
  console.log('Promise');
  setTimeout(function () {
    console.log('Promise-setTimeout');
  }, 10);
  resolve();
})
  .then(function () {
    setTimeout(function () {
      console.log('promise1-setTimeout');
    }, 10);
    console.log('promise1');
  })
  .then(function () {
    setTimeout(function () {
      console.log('promise2-setTimeout');
    }, 0);
    console.log('promise2');
  });

console.log('script end');

答案

先思考再查看答案哦~

运行结果

代码语言:javascript
复制
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout3
setTimeout4
promise2-setTimeout
setTimeout2
Promise-setTimeout
promise1-setTimeout
setTimeout1

运行步骤

  • 开始执行
    • 首先我们执行同步代码,先打印 script start
    • 再打印 async2 end,将 async2.then 放入微任务队列中。
    • 继续执行,将 setTimeout1setTimeout2setTimeout3setTimeout4 依次放入宏任务队列中。
    • 再打印 Promise,将 Promise.then 放入微任务队列中,将 Promise-setTimeout 放入宏任务队列中。
    • 最后打印 script end
  • 执行完成后,检查微任务队列不为空,按照先进先出原则继续执行。
    • 执行 async2.then 打印 async1 end
    • 执行 Promise.thenpromise1-setTimeout 放入宏任务队列中,打印 promise1,并将 promise1.then 放入微任务队列中。
  • 此时检查微任务队列继续不为空
    • 执行 promise1.thenpromise2-setTimeout 放入宏任务队列中,打印 promise2
  • 最后执行宏任务队列中的任务
    • 依次执行【setTimeout1setTimeout2setTimeout3setTimeout4Promise-setTimeoutpromise1-setTimeoutpromise2-setTimeout】。
    • 等待延迟时间到后,将其回调函数放入任务队列中。
  • 依次执行回调函数
    • 打印 setTimeout3
    • 打印 setTimeout4
    • 打印 promise2-setTimeout
    • 打印 setTimeout2
    • 打印 Promise-setTimeout
    • 打印 promise1-setTimeout
    • 打印 setTimeout1

参考来源

  • 并发模型与事件循环
  • EventLoop
  • Node EventLoop
  • JavaScript 垃圾回收
  • 阮一峰 EventLoop
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-01-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JavaScript 运行机制
  • EventLoop 事件循环
    • 介绍
      • MacroTask 宏任务
        • MicroTask 微任务
          • 同步任务和异步任务
            • 事件循环的进程模型
              • 总结
              • 举个栗子
                • 代码
                  • 执行结果
                    • 执行步骤
                      • 图例
                      • 再举个栗子
                      • 习题
                        • 题目
                          • 答案
                          • 参考来源
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档