JavaScript为什么需要事件循环Event Loop?对比浏览器执行环境和NodeJS执行环境
前言
JavaScript 是一门单线程的,非阻塞的,基于事件的编程语言。它是由 ECMAScript(JavaScript 语言核心)和一些浏览器提供的 API (如 DOM,AJAX 等)组成的。
这篇文章将一步步解析JavaScript的核心概念之一:事件循环,它是 JavaScript 运行时的主要组成部分,处理并执行任务,控制 JavaScript 的执行顺序,并允许非阻塞 I/O 操作。
什么是事件循环
事件循环是处理和执行任务的机制,它决定了 JavaScript 应用程序中代码的执行顺序。它提取任务(例如一段 JavaScript 代码或一些浏览器的 API 调用)并将其放入执行队列中。后者是一个存储待处理任务的队列。
为什么需要事件循环
JavaScript 的非阻塞 I/O 模型要求它能在等待异步操作(如网络请求、定时器、用户交互等)的返回结果时还能执行其它代码。事件循环允许 JavaScript 决定在得到这种异步结果时应该执行什么代码。
执行流程
常见的JavaScript执行环境有Node.js 和浏览器,虽然JavaScript 的事件循环是在执行环境中起着相同的作用,但它们的工作方式和处理异步事件的具体机制有一定的区别。
一、浏览器环境
JavaScript 的事件循环的执行流程如下:
-
初始化阶段
解析并执行初始化代码(例如全局脚本或函数)。
-
检查调用栈
如果调用栈为空,它会转到消息队列。如果不为空,当前堆栈顶部的任务会被弹出并执行。
-
消息队列检查
当调用栈为空,事件循环会查看消息队列中是否有待处理的任务。如果有,那么它会被删除并推到调用栈以供执行。
-
等待阶段
如果调用栈和消息队列都为空,那么事件循环会等待直到一个新的任务添加到消息队列里。
-
循环开始
事件循环会再次检查调用栈,并继续步骤2至步骤4,形成一个无限循环,或者直到全局执行环境关闭(例如,关闭浏览器页面或关闭 node.js 服务器)。
注意:任务可以是任何 JavaScript 代码段。它们可以是由事件(如点击或键盘事件)触发的段落,一段 setTimeout 计时器,或一个来自 AJAX 调用的响应等。
二、NodeJS环境
Node.js使用libuv库来创建和管理事件循环,以下是Node.js的事件循环的执行流程:
-
timers阶段
此阶段执行定时器的回调函数。例如:setTimeout 和 setInterval。
-
pending callbacks阶段
执行在上一轮循环中少数的系统操作的回调。
-
idle, prepare阶段
此阶段是libuv的内部阶段,用于准备下一轮的I/O事件。
-
poll阶段
除了设定的回调,其余关闭调用都在此阶段执行,此阶段也会处理新的I/O事件。
-
check阶段
在这个阶段调用setImmediate() 设置的回调函数。
-
close callbacks阶段
最后,如果有任何close事件,如:socket.on('close'...)等,会在这个阶段调用。
这是一个循环体,所以会不断地在这些阶段之间转换。对于每一个阶段,事件循环都会执行相应的回调,然后进入下一阶段。这就是为什么这个模型被称为事件循环。
总结
以上就是事件循环的基本概念,需要性和执行流程。学习 JavaScript时,理解事件循环机制的重要性不言而喻。透过这篇文章,希望能帮助你更好地理解 JavaScript 代码的运行机制。同样也帮助你更好地理解非阻塞 I/O 和 asynchronous/await 中的“异步”如何在 JavaScript 中工作。