Event Loop

135 阅读2分钟

一、进程与线程

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。

二、任务队列

Javascript从诞生就是单线程的,这意味着所有任务需要排队执行,前一个任务执行完了才会执行后面一个任务。如果前面任务执行时间过长,后面一直等着,这样太费时间了。
于是将任务分为两种,同步任务和异步任务,异步任务就是执行时间较长的任务。

  • 将所有同步任务放在主线程上执行,形成一个执行栈,栈和队列不同,栈是先进后出,就像子弹夹
  • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就把他放入任务队列中
  • 一旦"执行栈"中的所有同步任务执行完毕,主线程空了,就去任务队列中读取任务,按照顺序将任务放入执行栈中执行

三、宏任务和微任务

异步任务又分为宏任务和微任务  
  • 宏任务:macro-task包括:script setTimeout, setInterval, setImmediate, I/O, UI rendering。
  • 微任务:micro-task包括:process.nextTick, Promise, Object.observe, MutationObserver。
    当执行栈中的任务都在执行完了,主线程空出来,他就去任务队列中读取异步任务。任务队列又分为宏任务队列和微任务队列。 在读取异步任务时,先读取微任务队列,如果不为空,就一次先执行完所有的微任务,完成之后再去读取宏任务队列,每执行完一个宏任务就去查看微任务队列是不是为空,不是的话执行完当前宏任务后再去执行微任务,如此循环。。。

四、举个栗子

1.先来个简单的栗子:

setTimeout(()=>{
  console.log("setTimeout1");
  Promise.resolve().then(data => {
      console.log(222);
  });
});
setTimeout(()=>{
    console.log("setTimeout2");
});
Promise.resolve().then(data=>{
    console.log(111);
});

结果:

111
setTimeout1
222
setTimeout2

2.复杂点的栗子:

console.log('script start');

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

setTimeout(function () {
    console.log('setTimeout---200');
    setTimeout(function () {
        console.log('inner-setTimeout---0');
    });
    Promise.resolve().then(function () {
        console.log('promise5');
    });
}, 200);

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

结果:

script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0