译者注

本译文基本是按原文的意思来翻译,但对于 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. windows下利用线程池完成多任务的分配和运行

    在做项目的过程中有时候为了提升效率,用了多线程的方法来对任务进行分割和应用,后来发现,采用线程池的方法能更好的利用线程资源来计算任务,网上有很多关于如何运行线程池的例子,msdn上也给出了对应的例子: ...

  2. 10.OpenStack块存储服务

    添加块存储服务 安装和配置控制器节点 创建数据库 mysql -uroot -ptoyo123 CREATE DATABASE cinder; GRANT ALL PRIVILEGES ON cind ...

  3. Centos7更改网卡名为eth0

    1.先更该网卡配置文件设备名和网卡名参数: vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 NAME=eth0DEVICE=eth0 2.将配 ...

  4. 漂亮的弹窗口插件——sweetAlert的使用

    想必你已经受够了单调的alert弹窗吧? 为了更好的用户体验性,现在介绍一款漂亮的弹窗口插件——sweetAlert,现在就来介绍它的使用 1.首先在官网下载它的CSS和JavaScript文件:ht ...

  5. android launchmode singleinstance问题

    问题描述 最近测试关于launchmode的四种方式 默认模式 top singletask 都已经了解了 唯独这个instance模式 我的问题是 我们只作2个activity的假设A和B,其中A为 ...

  6. AC日记——Roma and Poker codeforces 803e

    803E - Roma and Poker 思路: 赢或输或者平的序列: 赢和平的差的绝对值不得超过k: 结束时差的绝对值必须为k: 当“?”时可以自己决定为什么状态: 输出最终序列或者NO: dp( ...

  7. springboot 2.0+ 自定义拦截器

    之前项目的springboot自定义拦截器使用的是继承WebMvcConfigurerAdapter重写常用方法的方式来实现的. 以下WebMvcConfigurerAdapter 比较常用的重写接口 ...

  8. js日常笔记

    写在前面: 在工作中,有时候会遇到一些零零碎碎的小知识点,虽然这些网上都可以查询到,但还是想把一些自己不是很熟悉的当做笔记记录下来,方便以后查询. 1.按钮隐藏/显示/可用/不可用 $("# ...

  9. TQ2440平台上LCD驱动的移植

    参考: http://liu1227787871.blog.163.com/blog/static/205363197201242393031250/ http://blog.csdn.net/cum ...

  10. ASIHTTPRequest学习(四)

    如果是IOS5的版本,可能集成过程中会遇到一些问题,我也找到了一些解决方案,比如,集成完后可能会遇到编译提示找不到"libxml/HTMLparser.h",解决这个问题可以参考这 ...