JS执行机制【Event Loop】

87 阅读3分钟

前言

JavaScript是一门单线程的非阻塞脚本语言,同一时刻只允许一个代码段执行。在单线程的机制下,执行异步任务时,在等待结果返回的这个时间段,后面的代码就无法执行了。

JS在执行代码时,遇到异步任务之后还有同步任务的场景时,它并不会等待异步任务执行完,而是先执行同步任务,那么JS是如何做到这一点的呢?

本篇文章将讲解下上述问题的执行过程,如有不正确之处,欢迎指正。

事件循环

单线程

讲事件循环之前,我们先来理解下为什么JS不设计成多线程的。

我们做个假设,如果JS是多线程的,因为JS有DOM API可以操作DOM,此时如果有两个线程在操作同一个DOM,线程1删除了这个dom节点,线程2要操作这个dom,就会产生矛盾,到底以哪个线程为主。

为了避免这种情况的出现,JS就被设计成了单线程

宏任务与微任务

JS引擎把所有任务分为两类:宏任务、微任务。

宏任务有:

  • script整体代码
  • setTimeout、setInterval
  • I/O
  • UI渲染
  • postMessage
  • MessageChannel
  • requestAnimationFrame
  • setImmediate(Node.js 环境)

微任务有:

  • new Promise.then()
  • MutaionObserver
  • process.nextTick(Node.js 环境)

执行规则

文章一开头我们了解到了单线程的弊端,JS是通过事件循环机制(EventLoop)来解决这一弊端的,接下来我们来看下EventLoop的执行规则:

  • 所有代码作为宏任务进入主线程执行栈,开始执行
  • 执行过程中,同步代码会立即执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完成出队,读取微任务队列,有则执行,直至全部执行完毕
  • 执行浏览器ui进程渲染
  • 检查是否有webworker任务,有则执行
  • 本轮宏任务执行完成,回到第2步,继续执行,直至宏任务与微任务队列全部清空

举例说明

我们了解完它的执行规则后,接下来我们举个例子来说明下,如下所示:

console.log("1"); 

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

process.nextTick(function() {
  console.log("6"); 
});

new Promise(function(resolve) {
  console.log("7"); 
  resolve();
}).then(function() {
  console.log("8"); 
});

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

process.nextTick()为node中的方法,你可以把它理解为与promise一样的微任务,promise的executor函数中的同步代码会立即执行。

我们来分析下上述代码的执行顺序,如下图所示:

event-loop.jpg

运行结果如下所示:

image.png

当你把上述示例代码吃透之后,相信你也理解了js的事件循环机制了。

当然,你可能没有那么快就啃透这个例子,这种概念性的东西,掌握它最好的办法就是:将示例代码放到编辑器里,对照着事件循环的执行规则,一行一行的去读代码,大脑过一遍,猜测运行结果,然后再去执行代码判断执行结果是否与你猜的一致。

最后,举一反三,去网上找一些事件循环的demo多加练习,熟练之后你就把这个知识点吃透了。

本文使用 文章同步助手 同步