JavaScript的事件循环机制Event Loop

407 阅读4分钟

1.为什么JavaScript是单线程的?

  • 我们都知道javascript从诞生之处就是一门为浏览器设计的单线程非阻塞的脚本语言,他的作用就是与浏览器交互,在浏览器中,我们需要进行各种各样的DOM操作。试想一下 如果javascript是多线程的,那么当两个线程同时对dom进行一项操作,例如一个向其添加事件,而另一个删除了这个DOM,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景,javascript选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务。那它是怎么做到不阻塞的呢?那就是我下面要讲的Event Loop了。

2 事件循环机制Event Loop

2.1执行栈和事件队列

当javascript代码执行的时候,所有的任务分为两种:同步任务和异步任务,同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入任务队列的任务,只有主线程通知任务队列,某个异步任务可以执行了,该任务才会进入主线程执行。

  • 所有的同步任务在主线程执行,形成一个执行栈
  • 主线程之外还有一个任务队列,当处于挂起的异步任务有了运行结果,就在任务队列之中放进这个事件
  • 一旦执行栈的所有同步任务执行完成,系统就会读取任务队列,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的最后一步。

我们可以用一张图来理解: 这里写图片描述

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。 举个例子:

let a = () => {
  setTimeout(() => {
    console.log("任务队列函数1");
  }, 0);
  for (let i = 0; i < 1000; i++) {
    console.log("a的for循环");
  }
  console.log("a事件执行完");
};

let b = () => {
  setTimeout(() => {
    console.log("任务队列函数2");
  }, 0);
  for (let i = 0; i < 1000; i++) {
    console.log("b的for循环");
  }
  console.log("b事件执行完");
};

a();
b();

执行结果:

"a的for循环"
...
"a的for循环"
"a事件执行完"
"b的for循环"
...
"b的for循环"
"b事件执行完"
"任务队列函数1"
"任务队列函数2"

3 宏任务Macrotask与微任务Microtask

  • 以上的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务(Microtask)和宏任务(Macrotask)。

javascript宏任务:

script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel

javascript微任务:

Promise.then
Object.observe
MutaionObserver

我们上面说到,在事件循环中,当一个异步任务返回结果后会放到任务队列中。然而根据这个异步任务的类型,可以看作放到对应的宏任务或则微任务队列中去,在当前调用栈为空的时候,主线程会查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈...如此反复,进入循环。

举个例子

setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})

上面代码中先执行new Promise里面的代码输出2,然后执行宏任务promise.then输出3,最后执行微任务输出1。

2
3
1