[译] JavaScript 的事件循环
译者注
本译文基本是按原文的意思来翻译,但对于 JavaScript 的事件循环,个人感觉还是 Philip Roberts 的视频讲解更形象些,思路和本文大致相同,不过他把事件表理解为 Web API,事件队列理解为任务队列或回调队列,童鞋们可以看下(可能需要科学上网):
Philip Roberts: Help, I’m stuck in an event-loop.
Philip Roberts: What the heck is the event loop anyway?
MDN:Concurrency model and Event Loop
原文:What is the JavaScript Event Loop?
简介
也许你和我一样,非常喜欢 JavaScript,我们使用它编写网页,通过它连接世界。是的,它并不完美,但尼玛这世上有完美的编程语言吗?!
JavaScript 的内部原理复杂而难懂,其中之一就是事件循环(Event Loop),可能有不少童鞋写了几百年的 JavaScript 代码,仍搞不清楚事件循环是个什么鬼,希望本文可以给小伙伴们一些帮助。
浏览器里的 JavaScript
说到 JavaScript,我们一般会想到浏览器,事实上,运行一个页面涉及到非常多的东西,例如 JavaScript 引擎(如谷歌浏览器的 V8 引擎)、Web API(如 DOM )、事件循环(Event Loop)和事件队列(Event Queue),看到这么一堆名词,你可能会觉得“卧槽,好复杂的样子……”,嗯,看起来确实挺复杂的,但你很快会看到,它们的工作原理也没想象中那么难懂。
在讨论事件循环之前,我们要先对 JavaScript 引擎有一个基本概念。
JavaScript 引擎
目前最流行的 JavaScript 引擎是谷歌浏览器的 V8 引擎,它不止应用于浏览器,还通过 NodeJs 活跃于服务端。那 JavaScript 引擎到底是干哈的呢?其实非常简单,它的工作就是遍历应用中的每一行 JavaScript 代码,逐行执行。没错,一次一行,这货是单线程的。也就是说,如果有某一行代码需要执行很长长长长长的时间,那么后面的代码就会被阻塞住。这肯定不是我们想要的,想象下你在网页中点了某个按钮,然后页面就懵逼了,尝试做其它操作,发现一点卵反应都没有,4不4瞬间就心中千万只草泥马在奔腾,说好的屠龙宝刀点击就送呢?而这一切的罪魁祸首就是一开始点击的那个按钮触发了一大坨需要执行的代码,导致后面的逻辑全部塞住了,便秘了魂淡。
那么,JavaScript 引擎是如何逐行执行 JavaScript 代码的呢?它使用了一个调用栈(call stack)。可以把调用栈想象成电梯,最先进去的人最后出来,而最后进去的反而最先出来。
举个栗子:
/* main.js */
var firstFunction = function () {
console.log("老子天下第 1!");
};
var secondFunction = function () {
firstFunction();
console.log("老子天下第 2!");
};
secondFunction();
/* 执行结果:
* => 老子天下第 1!
* => 老子天下第 2!
*/
在代码执行过程中,调用栈的变化如下:
- 执行 Main.js:

- 调用 secondFunction:

- 执行 secondFunction,调用 firstFunction:

- 执行 firstFunction,控制台输出“我是函数 1!”,因为 firstFunction 里没有其它代码需要执行,firstFunction 从调用栈中弹出:

- 继续执行 secondFuncction,控制台输出“我是函数 2!”,因为 secondFuncction 里没有其它代码需要执行,secondFuncction 从调用栈中弹出:

- 最后,因为 main.js 里没有其它代码需要执行,main.js 也从调用栈中弹出:

好吧,那我们可以讲事件循环了吗?
现在,我们已经了解了 JavaScript 引擎调用栈的工作原理,但到底要如何避免代码阻塞呢?非常幸运,JavaScript 提供了一种基于异步回调函数(asynchronous callback function)的机制。哇!是不是感觉刚从调用栈里爬出来,又掉到另一个坑里?不用担心,异步回调函数跟我们平时在 JavaScript 里写的其它函数并没有什么不同,只是它不会马上执行,而是在未来某个时间执行。如果你使用过 JavaScript 的 setTimeout 函数,那么你已经邂逅了异步回调函数。
来看个栗子:
/* main.js */
var firstFunction = function () {
console.log("老子天下第 1!");
};
var secondFunction = function () {
setTimeout(firstFunction, 5000);
console.log("老子天下第 2!");
};
secondFunction();
/* 执行结果:
* => 老子天下第 2!
* (5秒钟后)
* => 老子天下第 1!
*/
在代码执行过程中,调用栈的变化如下(我们跳过一些步骤,直接从变化处开始):
- 在 secondFuncction 进入调用栈后,setTimeout 函数被调用,也进入调用栈:

在 setTimeout 函数执行后,发生了一个特别的事情:引擎把 setTimeout 的回调函数(本例中为 firstFunction)放进了一个事件表(Event Table)。可以把事件表想象成登记台:调用栈告诉事件表登记一个特殊的函数,只有发生特定的事件时才执行。当这个特定事件发生时,事件表会把函数转移到事件队列(Event Queue)。事件队列就像一个集中待命区,函数在这里排队,等待被调用时进入调用栈。
你可能会有疑问:“那事件队列里的函数会在什么时候进入调用栈呢?” JavaScript 引擎遵循一个非常简单的规则:不断地检查调用栈空了没有,如果空了,则检查事件队列中有没有等待被调用的函数,如果有,则队列中第一个函数将被移入调用栈,如果事件队列是空的,则监控进程继续保持运行。看,这他喵的就是所谓的事件循环!
- 执行 setTimeout 函数,将其回调函数(本例中为 firstFunction)添加到事件表并为其注册一个5秒延迟事件:

- 见证奇迹的时刻来了!回调函数加入事件表后,没有造成任何阻塞!浏览器没有傻愣愣地等 5 秒,而是紧接着就执行了 secondFuncction 里下一行的 console.log:

- 事件表会不断监控是否有指定的事件发生,以把相应函数移入事件队列。现在,secondFunction 和 main.js 都执行完成了:

- 在 firstFunction 加入事件表 5 秒后,事件表将其移入事件队列:

- 因为事件循环在不断地监控调用栈,现在调用栈空了,firstFunction 将进入调用栈:

- 在 firstFunction 执行完成后,调用栈为空,事件表没有任何事件需要监控,事件队列也为空:

总结
本文的讲解忽略了 JavaScript 引擎、事件表、事件队列和事件循环的具体实现细节,但对于我们大多数前端开发而言,只需要简单了解 JavaScript 是如何执行一个异步函数就可以了,希望本文可以让小伙伴们对这些概念的基本原理有个比较清晰的理解。
[译] JavaScript 的事件循环的更多相关文章
- 对javascript EventLoop事件循环机制不一样的理解
前置知识点: 浏览器原理,浏览器内核5种线程及协作,JS引擎单线程设计推荐阅读: 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 [FE]浏览器渲染引擎「内核」 js异步编程,Promise ...
- JavaScript的事件循环机制浅析
前言 JavaScript是一门单线程的弱类型语言,但是我们在开发中,经常会遇到一些需要异步或者等待的处理操作. 类似ajax,亦或者ES6中新增的promise操作用于处理一些回调函数等. 概念 在 ...
- 从JavaScript的事件循环到Promise
JS线程是单线程运行机制,就是自己按顺序做自己的事,浏览器线程用于交互和控制,JS可以操作DOM元素, 说起JS中的异步时,我们需要注意的是,JS中其实有两种异步,一种是基于浏览器的异步IO,比如Aj ...
- 【运行机制】 JavaScript的事件循环机制总结 eventLoop
0.从个例子开始 //code-01 console.log(1) setTimeout(() => { console.log(2); }); console.log(3); 稍微有点前端经验 ...
- javascript的事件循环机制
JavaScript是一门编程语言,既然是编程语言那么就会有执行时的逻辑先后顺序,那么对于JavaScript来说这额顺序是怎样的呢? 首先我们我们需要明确一点,JavaScript是单线程语言.所谓 ...
- 深入理解JavaScript的事件循环(Event Loop)
一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为 因为它经常被用于类似如下的方式来实现 while (queue.waitForMessage()) ...
- [译]Javascript中的循环
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- 从一道题浅说 JavaScript 的事件循环
最近看到这样一道有关事件循环的前端面试题: //请写出输出内容 async function async1() { console.log('async1 start'); await async2( ...
- [译]Javascript timing事件
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
随机推荐
- 解决:eclipse 断点调试进入到class文件,无法查看变量值问题
今天团队一小伙伴调试项目时,一不小心选错了源文件目录(maven分模块项目),选到了顶层父项目下的文件,结果调试时发现无法查看调试过程中的变量值,要解决这个问题,其实很简单,稍稍配置一下就可以了,为了 ...
- 【转载】SSIS 64位环境访问Oracle11g
转载博客:http://www.dotblogs.com.tw/allanyiin/archive/2010/11/21/19585.aspx SSIS 为了要能够在64位的机器上面让SSIS存取Or ...
- 腾讯云通信WebIM事件回调的坑~
最近在开过工作中用到了腾讯IM的功能,由于业务的需要主要使用到了: 1.loginInfo 用户登录,用户信息 2.getRecentContactList 获得最近联系人 3.getLastGrou ...
- 请教一下16aspx上的源代码要如何在自己的服务器上运行
很正常呀,,我下载的也有运行不成功的你要去他们16aspx论坛发帖子问这里很少有人回答你这样的问题
- (1)C# 创建ef sqlserver
连接sql 如果报错不能连接的错误 把这三个IP地址的端口号设置上,并启用.第一个18.6是本机ip,之后就可以测试了 最后重启服务器
- AtCoder Grand Contest 012 B Splatter Painting (反向处理 + 记忆化)
题目链接 agc012 Problem B 题意 给定一个$n$个点$m$条边的无向图,现在有$q$个操作.对距离$v$不超过$d$的所有点染色,颜色编号为$c$. 求每个点最后的颜色状态. 倒过 ...
- 分层图【p2939】[USACO09FEB]改造路Revamping Trails
Description 约翰一共有N)个牧场.由M条布满尘埃的小径连接.小径可 以双向通行.每天早上约翰从牧场1出发到牧场N去给奶牛检查身体. 通过每条小径都需要消耗一定的时间.约翰打算升级其中K条小 ...
- 转换vmware的vmdk格式到qcow2或者raw格式
qemu-img convert xxxx-disk1.vmdk -f vmdk -O qcow2 xxxx-disk1.qcow2 qemu-img convert xxxx-disk1.vm ...
- ubuntu 下安装nodejs以及pm2
ubuntu 12.04服务器可以使用apt-get方式安装Node JS,但是,安装完后的版本为v0.6.12的版本,如果我们想要使用新一点的版本需要做如下配置: 1 2 3 4 apt-get i ...
- ASP.NET 5基础之中间件
来源https://docs.asp.net/en/latest/fundamentals/middleware.html 一些可以整合进http请求管道的小的应用组件称做中间件.ASP.NET 5集 ...