译者注

本译文基本是按原文的意思来翻译,但对于 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 的事件循环的更多相关文章

  1. 对javascript EventLoop事件循环机制不一样的理解

    前置知识点: 浏览器原理,浏览器内核5种线程及协作,JS引擎单线程设计推荐阅读: 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 [FE]浏览器渲染引擎「内核」 js异步编程,Promise ...

  2. JavaScript的事件循环机制浅析

    前言 JavaScript是一门单线程的弱类型语言,但是我们在开发中,经常会遇到一些需要异步或者等待的处理操作. 类似ajax,亦或者ES6中新增的promise操作用于处理一些回调函数等. 概念 在 ...

  3. 从JavaScript的事件循环到Promise

    JS线程是单线程运行机制,就是自己按顺序做自己的事,浏览器线程用于交互和控制,JS可以操作DOM元素, 说起JS中的异步时,我们需要注意的是,JS中其实有两种异步,一种是基于浏览器的异步IO,比如Aj ...

  4. 【运行机制】 JavaScript的事件循环机制总结 eventLoop

    0.从个例子开始 //code-01 console.log(1) setTimeout(() => { console.log(2); }); console.log(3); 稍微有点前端经验 ...

  5. javascript的事件循环机制

    JavaScript是一门编程语言,既然是编程语言那么就会有执行时的逻辑先后顺序,那么对于JavaScript来说这额顺序是怎样的呢? 首先我们我们需要明确一点,JavaScript是单线程语言.所谓 ...

  6. 深入理解JavaScript的事件循环(Event Loop)

    一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为 因为它经常被用于类似如下的方式来实现 while (queue.waitForMessage()) ...

  7. [译]Javascript中的循环

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

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

    最近看到这样一道有关事件循环的前端面试题: //请写出输出内容 async function async1() { console.log('async1 start'); await async2( ...

  9. [译]Javascript timing事件

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

随机推荐

  1. POCO库中文编程参考指南(6)Poco::Timestamp

    1 类型别名 三个时间戳相关的类型别名,TimeDiff表示两个时间戳的差,第二个是以微秒为单位的时间戳,第三个是以 100 纳秒(0.1 微妙)为单位的时间戳: typedef Int64 Time ...

  2. C++文件(夹)选择对话框

    由于各种应用,我们需要调用系统的打开文件对话框或者打开文件夹对话框,或两者兼有.今遇到这个情况已经解决,特写下这篇博文. 1.打开文件对话框常用的方法是使用系统的CFileDialog.这里介绍另外一 ...

  3. Xdebug安装对应版本与配置

    Xdebug安装地址https://xdebug.org/download.php,进入下载页面后点击custom installation instructions,可以找到适合的Xdebug版本. ...

  4. centos6.6部署mysql mmm高可用架构

    一.环境简述 1.工作逻辑图 2.MySQL-MMM优缺点 优点:高可用性,扩展性好,出现故障自动切换,对于主主同步,在同一时间只提供一台数据库写操作,保证的数据的一致性. 缺点:Monitor节点是 ...

  5. 解决ASP.NET裁剪图片失真

    //有的时候剪切图片时,出现图片失真的问题,是因为图片质量下降造成的 //按照指定的数据画出画板(位图) System.Drawing.Image imgPhoto = System.Drawing. ...

  6. MVC中路由器程序

    MVC中路由器程序编写方式如下例子 把地址:/home/add?id=1 改写成:/home/add/1 把地址:/home/edit?id=1&sid=2 改写成:/home/edit/1_ ...

  7. HDU 1203 【01背包/小数/概率DP】

    I NEED A OFFER! Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Tot ...

  8. 洛谷——P2660 zzc 种田

    P2660 zzc 种田 题目背景 可能以后 zzc就去种田了. 题目描述 田地是一个巨大的矩形,然而zzc 每次只能种一个正方形,而每种一个正方形时zzc所花的体力值是正方形的周长,种过的田不可以再 ...

  9. iOS 代理 重定向消息 forwardInvocation

    今天简单研究一下iOS的重定向消息forwardInvocation: 首先看看Invocation类: @interface NSInvocation : NSObject { @private _ ...

  10. SQL Server Profiler和数据库引擎优化顾问

    原文:SQL Server Profiler和数据库引擎优化顾问  简介 说到Sql的[性能工具]真是强大,SQL Server Profiler的中文意思是SQL Server事件探查,这个到底 ...