事件循环机制

javaScript是运行于浏览器环境的一门编程语言,其具有单线程、异步的特性。要想更好的去理解,就需要明白事件循环机制。

事件循环机制是浏览器为了适应javaScript这门语言的特性而内置的一种机制。

要弄明白这种机制,我们需要先强调这几个概念: 单线程、异步、执行队列、浏览的进程和线程

什么是单线程

先来说说什么是单线程吧,其实也很好理解!javaScript代码执行的时候需要有一个执行线程去做解析执行代码这件事,浏览器中只有一个执行线程。这也就解释了什么是单线程。

有的小伙伴就问了,那不能多设置一个执行线程吗?当然可以,如果再设置一个线程,那么理论上是可以实现两个线程一起去解释执行javaScript代码的。这样效率明显是提高的,但是为什么不这么做呢?

这就是另一个问题了,为什么javaScript要设置成单线程?

为什么javaScript是单线程的

想一想,浏览器中运行的javaScript的主要作用是干什么的?对,做视图以及交互的。那么不可避免的需要使用javaScript去操作dom。如果有两个线程在运行javaScript的话,那么会让界面变的不可控制。 举个例子:假设线程1把div.box的宽度变成了100px,然后基于100的宽度需要做一些操作。但是,另一个线程又将div.box设置成了200px,那这样的话视图就乱套了,写的程序也会出现问题。

想想就很可怕,但是幸好javaScript是单线程。

什么是异步

单线程的程序有个明显的问题,相信大家也发现了,那就是效率比较低,尤其是碰见了一些任务比较耗时的时候,效率问题就非常明显。为了解决这个问题,就引入了异步的概念。 所谓异步的概念就是,有些任务是耗时的但是又不占用js执行线程,这一类任务我们可以先将其挂起,等需要占用js执行线程的时候再放入执行队列。这样,整个程序的执行效率就会得到保障了。 所以说,在单线程的前提下,异步可以保障程序的执行效率。

浏览器的进程和线程

浏览器是多进程open in new window的,主要如下:

  • Browser进程(了解)
    • 浏览器的主进程(负责协调、主控),该进程只有一个。
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 讲渲染(Renderer)进程得到的内存中的Bitmap(位图),绘制到用户界面上
    • 网络资源的管理,下载等
  • 第三方进程
    • 每种类型的插件对应一个进程,当使用该插件时才创建
  • GPU进程
    • 该进程也只有一个,用于3D绘制等等
  • 渲染进程(重要)
    • 即通常所说的浏览器内核(Renderer进程,内部是多线程)
    • 每个Tab页面都有一个渲染进程,互补影响
    • 主要作用为页面渲染,脚步执行,事件处理等

Renderer进程的主要线程

Renderer进程主要负责页面的渲染,JS的执行,事件的循环,该进程下有多个线程open in new window

  • GUI渲染线程
  • js引擎线程
  • 事件触发线程
  • 定时器触发线程
  • 异步http请求线程

其中 GUI渲染线程和js引擎线程是互斥的,也就是说当GUI渲染线程执行的时候,那么js引擎线程肯定是不执行的,同样的js引擎执行的时候,GUI渲染线程也是停止的。

js引擎线程是维护着两个执行队列(宏任务队列和微任务队列)的,js引擎线程会依次执行队列里的任务的,等一个队列执行结束会去执行另一个队列。

所以,js引擎线程和GUI渲染线程的交替执行顺序如下:

js引擎执行宏任务队列 -> js引擎执行微任务队列 -> GUI进行渲染任务 -> js引擎执行宏任务队列 -> js引擎执行微任务队列 -> GUI进行渲染任务 ....

宏任务和微任务

常见的宏任务

  • 主代码块
  • setTimeout
  • setInterval
  • setImmediate - node
  • requestAnimationFrame

常见的微任务

  • process.nextTick - Node
  • Promise.then
  • catch
  • finally

一道面试题

通过下边的面试题,测测自己是否掌握了事件循环

// 请说出输出顺序并说明原因
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end');
Last Updated:
Contributors: taotao