JS 事件循环一次说清

JS 事件循环一次说清

从单线程说起

我们都知道 JavaScript 是单线程语言,那为什么 JavaScript 不支持多线程呢,这是因为 JavaScript 本身设计之初就是按照单线程设计的。JavaScript 设计之初的目的是在浏览器端进行运行,用于处理页面操作的也就是操作DOM,因此他的用途就决定了的他是单线程的语言,因为多线程会带来线程间同步的问题。

宏任务与微任务

JavaScript 是单线程语言,这也就意味着所有的任务都需要排队,一个一个按照顺序执行。但是当遇到很慢的 IO 设备时,例如网络请求等,后续的任务就需要等待网络请求任务完成才能继续执行,这就会导致 CPU 处于空闲的挂起状态,因此为了不阻塞后续的任务,任务又分为同步任务和异步任务两种。同步任务在主线程进行依次执行,而异步任务会进入任务队列 中,任务队列中的任务不会阻塞主线程的执行,只有当任务队列内的异步任务可以执行时才会加入到主线程进行执行。

同步任务和异步任务的执行机制简单如下:

  1. 同步任务会直接在主线程中依次执行
  2. 异步任务会加入到任务队列中
  3. 当主线程中同步任务执行然后会读取任务队列执行任务队列内的任务
  4. 重复上面的循环

常见宏任务:

  • script
  • setTimeout, setInterval 等定时器
  • 事件触发的回调函数,如 DOM 事件

常见微任务:

  • Promise
  • MutationObserver

事件循环

主线程同步任务执行完,读取任务队列执行异步任务,不停的循环这个过程就是事件循环 。在 Node.js 环境中和浏览器环境中,事件循环执行的顺序有一些差别:

  • Node.js 独有的 process.nextTick 微任务总是最先执行,之后才会执行其他的微任务
  • 在 Node.js 11.x 之前每次都是执行完所有的宏任务后才会执行各个宏任务的微任务。而在 11.x 之后,则为当一个宏任务执行完后则会立即执行其相关的微任务,这样的改动和目前浏览器的执行机制保持一致。

实际案例

讲了这么多实际理解起来还是比较抽象,我们来看几个例子:

例子一

javascript 复制代码
console.log("1");

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

new Promise((resolve) => {
  console.log("3");
  resolve("4");
})
  .then((res) => {
    console.log(res);
  })
  .then(() => {
    console.log("5");
  });

Promise.resolve()
  .then(() => {
    console.log("6");
  })
  .then(() => {
    console.log("7");
  });

console.log("8");

// 执行结果:1 3 8 4 6 5 7 2 

解析:

  • 首先在第一次宏任务中会先打印出 1 2 8,因为Promise 的构造函数属于是同步任务,因此会先打印出来。
  • 第一次的宏任务后会执行微任务,其中包括第一个的 Promise 的第一个 then 4 和第二个Pormise 的第一个 then 6,因为then 函数本身会放到下一次微任务中执行,因此第二次执行的 then 会放到下次微任务中执行而延后。
  • 紧接着第一次微任务,会继续执行后续的微任务,即 5 和 7。
  • 最后执行由于 setTimeout 回调产生的第二次宏任务,打印出 2。

例子二

scss 复制代码
console.log(1);

setTimeout(() => {
  console.log(2);
  process.nextTick(() => {
    console.log(3);
  });
  new Promise((resolve) => {
    console.log(4);
    resolve();
  }).then(() => {
    console.log(5);
  });
});

new Promise((resolve) => {
  console.log(7);
  resolve();
}).then(() => {
  console.log(8);
});

process.nextTick(() => {
  console.log(6);
});

setTimeout(() => {
  console.log(9);
  process.nextTick(() => {
    console.log(10);
  });
  new Promise((resolve) => {
    console.log(11);
    resolve();
  }).then(() => {
    console.log(12);
  });
});

// 执行结果:1 7 6 8 2 4 3 5 9 11 10 12

解析:

  • 首先第一次宏任务会先打印出 1 7。
  • 紧接着进入微任务,process.nextTick 是Node.js中特有的,他会在微任务中优先执行,因此,当前微任务输出的顺序是 6 8,而非 8 6。
  • 之后进入第二次宏任务,为 setTimeout 的回调函数,第一个宏任务优先输出 2 4。宏任务执行完后会立即执行其对应的微任务,输出 3 5。
  • 之后为第二个宏任务,其逻辑与上一个宏任务一致,先输出 9 11,后再执行其相关的微任务,输出 10 12。
相关推荐
尾善爱看海4 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
李明卫杭州9 小时前
React 复合事件系统对比解析
前端·javascript·react.js
LEE10 小时前
前端转型全栈 01:数据建模,前端最大的盲区
前端·javascript·后端
雪芽蓝域zzs11 小时前
第四十七节:驾驶舱大屏 ECharts 图表集成
前端·javascript·vue.js
梦醒沉醉12 小时前
7、表达日期和时间(前朝遗老Date)
javascript
雪芽蓝域zzs12 小时前
第四十九节:TagsView 右键菜单(带三角箭头)给每个 tag 增加**鼠标右键菜单**(右键标签弹出:关闭、关闭其他、关闭全部)
前端·javascript·vue.js
এ慕ོ冬℘゜12 小时前
从零开发移动端医院挂号页面:适配、交互与前端实现详解
前端·javascript
宁渡AI大模型13 小时前
AI 全栈面试新趋势:Vibe Coding、前端、Java 后端高频面试题深度解析|河南宁渡科技有限公司编程教程
java·javascript·人工智能·python·ai大模型
东风微鸣13 小时前
AI依赖更新别裸奔🚨
javascript
研☆香13 小时前
echarts图表的使用方法
javascript