译者注

本译文基本是按原文的意思来翻译,但对于 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. 4.DataFrame(快速开始)

    快速开始 基本概念 ''' 在使用 DataFrame 时,需要了解三个对象上的操作:Collection(DataFrame) ,Sequence,Scalar Collection(DataFra ...

  2. 使用composer出现 Cannot find module (SNMPv2-TC) 等错误的解决方法

    Cannot find module (SNMPv2-TC): At line 10 in /usr/share/snmp/mibs/UCD-DLMOD-MIB.txt Cannot find mod ...

  3. [BZOJ1082][SCOI2005]栅栏 二分+搜索减枝

    1082: [SCOI2005]栅栏 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2430  Solved: 1034[Submit][Status ...

  4. CF987C Three displays【一维DP/类似最大子序列和】

    [链接]:CF987C [分析]:先求出每个s[i]后面比s[i]大的c[i]的最小值,然后枚举前两个数c(i),c(j)以及 j 后面递增且存在最小值的dp(j) [代码]: #include< ...

  5. spark完整的数据倾斜解决方案

    1.数据倾斜的原理 2.数据倾斜的现象 3.数据倾斜的产生原因与定位 在执行shuffle操作的时候,大家都知道,我们之前讲解过shuffle的原理. 是按照key,来进行values的数据的输出.拉 ...

  6. 新博客:11101001.com

    开了一个新blog 但还是会用这个写博客 新博客地址11101001.com

  7. xcode里面使用Memory Leaks和Instruments检测内存泄漏

    教程截图: 作为一名无证程序员,无论你多么精通Objective-C的内存管理,随着时间的推移,你也不可避免的犯内存相关的错误.但通常因为代码量太大,以至于你不可能一行一行的去排除(等你解决完,你设计 ...

  8. @selector和SEL

    遇到selector发现不是很明白,网上搜到的零零星星的介绍也不成体系,索性自己翻译一下,加深一下印象.原文来自官方API文档下的Selectors. Selectors 在OC中,selector有 ...

  9. 在React组件unmounted之后setState的报错处理

    最近在做项目的时候遇到一个问题,在 react 组件 unmounted 之后 setState 会报错.我们先来看个例子, 重现一下问题: class Welcome extends Compone ...

  10. linux mysql cluser集群

    管理节点的安装与启动 config.init内容如下 [NDBD DEFAULT] NoOfReplicas=1 #定义在Cluster环境中相同数据的份数,最大为4 [NDB_MGMD] #设置管理 ...