这样回答事件循环

289 阅读5分钟

说起事件循环,可从以下三方面来回答和学习

  1. 基础知识:js单线程、异步编程、事件循环任务类型,宏任务和微任务
  2. 事件循环过程
  3. 程序执行顺序:输出结果以及阐述运行流程

1、基础知识

1.1 js单线程

javascript是单线程的,就意味着同一时间只能做一件事。如果上面的代码很耗时如定时器、网络请求等,下面的代码就无法执行。
为了防止javascript的主线程不阻塞,eventLoop(事件循环),任务队列产生。

1.2 异步的概念及目的

异步:程序中现在运行的部分和将来运行的部分之间的关系,就是异步编程的核心。
目的:异步的出现,是为了解决程序阻塞
程序中将来执行的部分,并不一定在现在运行的部分执行完之后就立即执行。换句话来说,现在无法完成的任务将会异步完成,因此并不会出现人们本能认为的阻塞行为。
异步解决方式:从现在到将来的“等待”,最简单的方法是使用一个通常成为回调函数的函数。后续出现promiseasync await等优秀的解决方案。

1.3 异步机制的引入

任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点击、ajax响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序中引入了异步机制。

1.4 事件循环的任务类型

js中把任务分为了同步任务异步任务
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时器等

1.5异步任务包括两种类型:宏任务和微任务

宏任务和微任务区别如下:

宏任务微任务
谁发起的宿主(Node , 浏览器WindowJS引擎
具体事件1、定时器setTimeoutsetInterval
2、script全部代码(外层同步代码)
3、DOM事件
4、HTTP请求(ajaxfetchjsonp
1、Promise (thencatchfinally...)
2、async await
3、Process.nextTickNode独有)
4、mutationObserverhtml5新特性)
谁先运行后运行先运行
会触发新一轮的tick不会

注:
js运行宿主: javascript引擎并不是独立运行的,它运行在宿主环境中,对多数开发者来说通常就是web浏览器。近几年出现了其他的环境,如node.js
但所有的环境都有一个共同点,他们都提供了一种机制来处理程序中多个块的执行,且执行每块时调用javascript引擎,这种机制称为事件循环 换句话说,javascript引擎本身并没有时间的概念,只是一个按需执行js任意代码片段的环境,“事件”调度总是由包含它的环境进行。

2、事件循环过程

事件循环步骤:
1、执行主线程代码,执行同步任务代码。遇到微任务放到微任务事件队列,遇到宏任务放到宏任务事件队列。所有主线程任务完成之后,接下来读取异步队列中的异步任务
2、去异步微任务队列中查找。找到可执行条件的异步微任务,交给主线程执行
3、去异步宏任务队列中查找。找到可执行条件的异步宏任务,交给主线程执行

第一个图为步骤一(主线程和异步队列)
第二个图为步骤二和三(异步队列:宏任务和微任务) eventloop.png

queue.png

3、程序执行顺序

3.1 promise和setTimeout执行顺序

setTimeout(function () {
  console.log(1);
}, 0);
new Promise(function (a, b) {
  console.log(2);
  for (var i = 0; i < 10; i++) {
    i == 9 && a();
  }
  console.log(3)
}).then(function () {
  console.log(4)
});
console.log(5);

运行结果:
2 3 5 4 1
代码分析:

  1. setTimeout宏任务,放到异步宏任务队列中
  2. 执行Promise,输出2。then()放到异步微任务队列中
  3. 执行代码,输出5
  4. 同步代码执行完成后,执行异步微任务队列,输出4
  5. 5.执行异步宏任务代码输出1

3.2 async await执行顺序

async function async1() {
  const res = 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')
  })

运行结果:
async2 end Promise async1 end promise1 promise2 setTimeout
代码分析:
1. 执行代码,执行async1(),调用async2(),输出async2 end, 并返回一个Promise。遇到await会让出主线程,将await async2()放到异步微任务队列中
2. setTimeout是宏任务,放到异步宏任务队列中
3. Promise构造函数,输出Promise。两个then()方法是异步微任务,放到异步微任务队列中。
4. 同步代码执行完成,执行异步微任务队列,按照队列的先进先出的原则,依次输出 async1 end promise1 promise2
5. 执行异步宏任务队列,输出setTimeout

3.3 promise嵌套setTimeout

console.log('script start')
setTimeout(function () {
  console.log('timeout1')
}, 10)
new Promise((resolve) => {
  console.log('promise1')
  resolve()
  setTimeout(() => {
    console.log('timeout2')
  }, 10)
}).then(function () {
  console.log('then1')
})
console.log('script end')

运行结果:
script start promise1 script end then1 timeout1 timeout2
相信看到这里,最后一个题已经是小case了,就不阐述分析过程啦。

本文是在我梳理Promsie过程中,发现需要回顾异步及事件循环写下的一篇文章。如果想对Promise有更深入的了解,可以看我的《从浅入深的promise》这系列的文章。

本人能力有限,尚且不能深入解读,如果想要深入学习,可以关注冴羽的javascript深入系列:juejin.cn/column/7035…