资讯专栏INFORMATION COLUMN

Event Loop - JS执行机制

muddyway / 2705人阅读

摘要:心塞塞根据规范,事件循环是通过任务队列的机制来进行协调的。等便是任务源,而进入任务队列的是他们指定的具体执行任务回调函数。然后当前本轮的结束,主线程可以继续取下一个执行。

依然是:经济基础决定上层建筑。

说明

首先,旨在搞清常用的同步异步执行机制

其次,暂时不讨论node.js的Event Loop执行机制,以下关于浏览器的Event Loop执行机制

最后,借鉴了很多前辈的研究文章,非常感谢,此文主要是梳理所学,还请保持质疑以追求正确的知识

要点

基本概念

同步异步操作

Event Loop

基本概念

先解释现代js引擎几个概念。


stack(栈):这里放着js正在执行的任务。理解事件循环一(浅析)一文有对 stack 的 example 解释。

heap(堆):一个用来表示内存中一大片非结构化区域的名字,对象都被分配在这。

queue(队列):一个 js runtime 包含了一个任务队列,该队列是由一系列待处理的任务组成。而每个任务都有相对应的函数。当栈为空时,就会从任务队列中取出一个任务,并处理之。当该任务处理完毕后,栈就会再次为空。(queue的特点是先进先出(FIFO))。

为了方便描述与理解,作出以下约定:

stack 栈为主线程

queue 队列为任务队列(等待调度到主线程执行)

同步异步

js 是一门单线程语言。 js 引擎有一个主线程(main thread)用来解释和执行 js 程序,实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在node.js中)等等。这些线程可能存在于 js 引擎之内,也可能存在于 js 引擎之外,在此我们不做区分。不妨叫它们工作线程。但是前辈们颇有一种小本本记好的说法,那就是,要相信 js 单线程的本质,其他一切看似多线程,都是纸老虎。哈哈哈哈哈哈哈哈哈哈哈哈哈......

任务分为同步任务(synchronous)和异步任务(asynchronous),如果所有任务都由主线程来处理,会出现主线程被阻塞而使得页面“假死”。为了主线程不被阻塞,异步任务(如:AJAX异步请求,定时器等)就会交给工作线程来处理,异步任务完成后将异步回调函数注册进任务队列,等待主线程空闲时调用。流程如图:


// example
console.log("example-start")

setTimeout(() => {
  console.log("setTimeout-0")
}, 0)

console.log("example-end")

/* chrome result
 * 
    example-start
    example-end
    setTimeout-0
 *
 */

上面一个简单的小 js 片段的执行过程:

主线程开始同步任务执行,执行console.log("example-start")

然后接下来,主线程遇见一个异步操作setTimeout,将改异步任务交给工作线程处理,异步任务完成之后,将回调函数注册进任务队列,等待被调用

继续同步任务处理,执行console.log("example-end")

主线程空闲,调用任务队列中等待执行的回调函数,执行console.log("setTimeout-0")

最后借用Philip Roberts的生动形象的一张图,callback queue可以简单理解为任务队列,详细的下面会讲。


Event Loop

然而Event Loop并没有上面图中描述那么简单。心塞塞 : (

根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。

setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务(回调函数)。来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

仔细查阅规范可知,异步任务可分为 task(部分文章也称为 macro-task) 和 micro-task 两类,不同的API注册的异步任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。

task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(node.js 环境)

micro-task主要包含:Promise.then、MutaionObserver、MessageChannel、process.nextTick(node.js 环境)

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

在此次 tick 中选择最先进入队列的任务(oldest task),如果有则执行(一次)

检查是否存在 micro-task,如果存在则不停地执行,直至清空 micro-task queue

更新 render

主线程重复执行上述步骤

一个事件循环(Event Loop)中,主线程从任务队列中取出一个任务 task 执行时,而这个正在执行的任务就是从 task queue(部分文章也称为 macro-task queue)中来的。当这个 task 执行结束后,js 会将 micro-task queue中所有 micro-task 都在同一个 Event Loop 中执行,当这些 micro-task 执行结束后还能继续添加 micro-task 一直到整个 micro-task 队列执行结束。然后当前本轮的 Event Loop 结束,主线程可以继续取下一个 task 执行。所以更详细的 Event Loop 的流程图如下:

// example
console.log("example-start")

setTimeout(() => {
  console.log("setTimeout-0") // setTimeout-1
}, 0)

new Promise((resolve, reject) => {
  console.log("promise-1")
  resolve("promise-2")
  Promise.resolve().then(() => console.log("promise-3")) // then-1
}).then((response) => { // then-2
  console.log(response)
  setTimeout(() => {
    console.log("setTimeout-10") // setTimeout-2
  }, 10)
})

console.log("example-end")

/* chrome result
 * 
    example-start
    promise-1
    example-end
    promise-3
    promise-2
    setTimeout-0
    setTimeout-10
 *
 */

上面一个简单的 js 片段的执行过程:

第一轮事件循环:

第二轮事件循环:

第三轮事件循环:

如果上文理解有误或者有疑惑,欢迎交流。

参考

Philip Roberts: Help, I’m stuck in an event-loop.

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

关于JavaScript单线程的一些事

从一道题浅说 JavaScript 的事件循环

Event Loop的规范和实现

这一次,彻底弄懂 JavaScript 执行机制

好记性不如烂笔头。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95264.html

相关文章

  • 10分钟理解JS引擎的执行机制

    摘要:深入理解引擎的执行机制灵魂三问为什么是单线程的为什么需要异步单线程又是如何实现异步的呢中的中的说说首先请牢记点是单线程语言的是的执行机制。 深入理解JS引擎的执行机制 1.灵魂三问 : JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setTimeout 首先,请牢记2...

    zzbo 评论0 收藏0
  • JavaScript执行机制、事件循环

    摘要:曾经的理解首先,是单线程语言,也就意味着同一个时间只能做一件事,那么为什么不是多线程呢这样还能提高效率啊假定同时有两个线程,一个线程在某个节点上编辑了内容,而另一个线程删除了这个节点,这时浏览器就很懵逼了,到底以执行哪个操作呢所以,设计者把 Event Loop曾经的理解 首先,JS是单线程语言,也就意味着同一个时间只能做一件事,那么 为什么JavaScript不是多线程呢?这样还能提...

    rose 评论0 收藏0
  • Javascript 运行机制详解,Event Loop

    摘要:主线程在任务队列中读取事件,这个过程是循环不断地,所以这种运行机制叫做事件循环是在执行栈同步代码结束之后,下一次任务队列执行之前。 单线程 javascript为什么是单线程语言,原因在于如果是多线程,当一个线程对DOM节点做添加内容操作的时候,另一个线程要删除这个DOM节点,这个时候,浏览器应该怎么选择,这就造成了混乱,为了解决这类问题,在一开始的时候,javascript就采用单线...

    Jingbin_ 评论0 收藏0
  • 深入理解js引擎的执行机制

    摘要:深入理解引擎的执行机制最近在反省,很多知识都是只会用,不理解底层的知识。在阅读之前,请先记住两点是单线程语言的是的执行机制。所以,是存在异步执行的,比如单线程是怎么实现异步的场景描述通过事件循环,所以说,理解了机制,也就理解了的执行机制啦。 深入理解js引擎的执行机制 最近在反省,很多知识都是只会用,不理解底层的知识。所以在开发过程中遇到一些奇怪的比较难解决的bug,在思考的时候就会收...

    feng409 评论0 收藏0
  • 理解JS中的Event Loop机制

    摘要:前言前几天在理解的事件环机制中引发了我对浏览器里的好奇。接下来理解浏览器中的,先看一张图堆和栈堆是用户主动请求而划分出来的内存区域,比如你,就是将一个对象存入堆中,可以理解为存对象。废话不多说,直接上图个人理解。参考资料运行机制详解再谈 前言 前几天在理解node的事件环机制中引发了我对浏览器里Event Loop的好奇。我们都知道javascript是单线程的,任务是需要一个一个按顺...

    MASAILA 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<