Event loop

283 阅读5分钟

   要了解javascript的事件循环机制,我们需要从浏览器的进程,渲染机制说起。

    浏览器是多进程的,而主进程只有一个,负责浏览器界面的显示,与用户交互,负责各个页面的管理,创建和销毁其他进程,其次,第三方插件进程,只有使用该插件是才给他创建一个进程,每个页面也会开一个进程,重点是要说一下浏览器的渲染进程。


浏览器的渲染进程,主要包括页面的渲染,JS的执行,事件的循环。浏览器的渲染进程是多线程的:

1、GUI渲染线程

  • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
  • 当界面需要重绘或引发回流时,该线程就会执行
  • GUI渲染线程与JS引擎线程是互斥的

2、JS引擎线程

  • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
  • JS引擎线程负责解析Javascript脚本,运行代码。
  • JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序

3、事件触发线程

  • 归属于浏览器而不是JS引擎,用来控制事件循环
  • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
  • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
  • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4、定时触发器线程

  • 传说中的setInternalsetTimeout所在线程
  • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
  • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
  • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

5、异步http请求线程

  • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
  • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

更详细的讲解,请移步:从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

javascript 的运行机制

这个时候,浏览器页面初次渲染完成,我们来看JS引擎的一些运行机制。JS引擎是单线程,为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。


这里就直接引用一张图片来协助理解:(参考自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)


宏任务与微任务

宏任务:setTimeout, setInterval, setImmediate, I/O

微任务:原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver, MessageChannel

当主线程执行完毕,如果微任务队列中有微任务,则会先进入执行栈,当微任务队列没有任务时,才会执行宏任务的队列。

console.log(1);
setTimeout(function(){
    console.log(2);
    Promise.resolve(1).then(function(){
        console.log('promise')
    })
})
setTimeout(function(){
    console.log(3);
})

在浏览器中执行:
1
73084
2
promise
3

Node环境下的Event Loop

把上面的代码在node中执行,你会看到不一样的结果:

1
2
promise
3

这是为什么呢?先了解下node.js的运行机制

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8引擎再将结果返回给用户。

Event loop的事件处理机制如何运作

分为5个阶段:

  • timers: 执行setTimeout() 和 setInterval() 预先设定的回调函数。
  • I/O callbacks: 大部分执行都是timers 阶段或是setImmediate() 预先设定的并且出现异常的回调函数事件。
  • idle, prepare: nodejs 内部函数调用。
  • poll: 搜寻I/O事件,nodejs进程在这个阶段会选择在该阶段适当的阻塞一段时间。
  • check: setImmediate() 函数会在这个阶段执行。
  • close callbacks: 执行一些诸如关闭事件的回调函数,如socket.on('close', ...) 。
  • 详解请阅读:Nodejs 解读event loop的事件处理机制

    参考文章:

    从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

    javascript 运行机制详解:再谈Event Loop

    Nodejs 解读event loop的事件处理机制