Event Loop(事件循环)

发表于:2023-01-03
字数统计:3.1k 字
阅读时长:8 分钟
阅读量:161

js是单线程的,一次只能执行一段代码。单线程会导致很多任务需要排队,一个个去执行,如果此时某个任务执行时间太长,就会出现阻塞,为了解决这个问题,js引入了事件循环机制。

为什么要区分宏任务和微任务?

js是单线程的,但是分同步异步

微任务和宏任务皆为异步任务,它们都属于一个队列

宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI、 renderingsetImmediate(Node环境)

微任务:promise.then、Object.observe、MutationObserver、process.nextTick(Node环境)

先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务

任务优先级:

先执行同步代码,遇到宏任务则将宏任务放入宏任务队列中,遇到微任务则将微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

这里容易产生一个错误的认识:就是微任务先于宏任务执行。实际上是先执行同步任务然后在执行异步任务,异步任务是分宏任务和微任务两种的。

看一个例子,最后结果输出啥?

setTimeout(() => {
  console.log(1)
}, 0)

new Promise((resolve) => {
  console.log(2)
  resolve()
  console.log(3)
}).then(() => {
  console.log(4)
})

console.log(5)

分析:

1、遇到 setTimout(宏任务),放入宏任务队列中

2、遇到 new Promise,在实例化的过程中所执行的代码都是同步进行的,所以输出 2 和 3

3、Promise.then(微任务),将其放入微任务队列中

4、遇到同步任务 console.log(5) 输出5,主线程中同步任务执行完

5、从微任务队列中取出任务到主线程中,输出 4,微任务队列为空

6、从宏任务队列中取出任务到主线程中,输出 1,宏任务队列为空,事件循环结束

最后结果为:2 3 5 4 1

例子2:

setTimeout(() => {
  new Promise(resolve => {
    resolve()
  }).then(() => {
    console.log(1)
  })
  console.log(2)
})

new Promise(resolve => {
  resolve()
  console.log(3)
}).then(() => {
  console.log(4)
  Promise.resolve().then(() => {
    console.log(5)
  }).then(() => {
    Promise.resolve().then(() => {
      console.log(6)
    })
  })
})
console.log(7)

分析:

  1. 遇到setTimeout(宏任务),将 console.log(2) 放入宏任务队列中;
  2. 遇到 new Promise,在实例化的过程中所执行的代码都是同步进行的,所以输出 3;
  3. 而 Promise.then(微任务),将其放入微任务队列中
  4. 遇到同步任务 console.log(7),输出 7 ;主线程中同步任务执行完
  5. 从微任务队列中取出任务到主线程中,输出 4 ,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
  6. 从微任务队列中取出任务 a 到主线程中,输出 5;
  7. 从微任务队列中取出任务 b 到主线程中,任务 b 又注册了一个微任务 c,放入微任务队列中;
  8. 从微任务队列中取出任务 c 到主线程中,输出 6,微任务队列为空
  9. 从宏任务队列中取出任务到主线程,此任务中注册了一个微任务 d,将其放入微任务队列中,接下来遇到输出 2,宏任务队列为空
  10. 从微任务队列中取出任务 d 到主线程,输出 1,微任务队列为空

最后结果为:3 7 4 5 6 2 1

例子3:

console.log(1)
setTimeout(function () {
  console.log(2)
  new Promise(function (resolve) {
    console.log(3)
    resolve()
  }).then(function () {
    console.log(4)
  })
})

new Promise(function (resolve) {
  console.log(5)
  resolve()
}).then(function () {
  console.log(6)
})

setTimeout(function () {
  console.log(7)
  new Promise(function (resolve) {
    console.log(8)
    resolve()
  }).then(function () {
    console.log(9)
  })
})
console.log(10)

分析:

  1. 遇到同步任务 console.log(1) 输出1;
  2. 遇到 setTimeout(宏任务),放入宏任务队列中;
  3. 遇到 Promise,new Promise 在实例化的过程中所执行的代码都是同步进行的,所以输出 5,所以接着执行遇到 .then;
  4. 执行 .then(微任务),被分发到微任务 Event Queue 中;
  5. 遇到 setTimeout,宏任务,放入宏任务队列中;
  6. 遇到同步任务 console.log(10) 输出10,主线程中同步任务全部执行完;
  7. 从微任务队列中取出任务到主线程中,输出6;
  8. 在从宏任务队列中取出任务到主线程中,执行第一个setTimeout,输出2,3,4(在宏任务中执行同步,同步,微任务);
  9. 在执行第二个 setTimeout,输出7,8,9;

最后结果为:1,5,10,6,2,3,4,7,8,9

例子4:

new Promise((resolve) => {
  resolve(1)
  new Promise((resolve) => {
    resolve(2)
  }).then(data => {
    console.log(data)
  })
}).then(data => {
  console.log(data)
})

console.log(3)

分析:

  1. 遇到 new Promise 没有输出,往下走,然后又遇到了 new Promise 也没有输出,接着往下走,遇到 then(微任务)被分发到任务队列中,里面的走完了接着走外面的 then 同样也是分发到任务队列中;
  2. 接着遇到同步任务 console.log(3) 输出3,主线程中同步任务执行完毕;
  3. 从微任务队列中取出任务添加到主线程中,输出 2 和 1,微任务执行完毕,任务队列为空;

最后结果为:3 2 1

1/0