译—JavaScript是如何工作的(1):js引擎、运行时和调用栈的概述

668 阅读6分钟

原作者:blog.sessionstack.com/@zlatkov

随着JS变得越来越流行,开发团队们在多个级别的堆栈中都借力于js的支持- 前端,后台,混合式应用开发,嵌入式设备等等。

这篇博文旨是我们深入挖掘JavaScript和其工作原理的系列文章的第一篇:我们认为通过了解JavaScript的构建块和他们是如何共同发挥作用的,你能写出更好的代码和应用。我们也会分享我们在构建的一个轻量级的但必须具有强大和高性能的特点才能保持竞争力的JavaScript应用SessionStack时的一些经验法则。

正如GitHut Stats上的数据所示,JavaScript在github上的包活跃度和推送量居于领先地位。它也没有落后于其他类别的库。


查看github上最新的统计信息

如果项目依赖JavaScript越来越多,则意味着开发者不得不用对其内部原理越来越深的理解来利用好语言和其生态系统所提供的一切以构建令人惊奇的软件。

事实证明,虽然有大量的开发者每天都会使用JavaScript,但是他们并不知道引擎盖下发生了什么。

概述 

几乎人人都已经听说过V8引擎这个概念,大多数人也知道JavaScript是单线程或使用回调队列。

在本文中,我们将会详细地了解这些概念和解释JavaScript实际上是怎样运行的。知道了这些细节后,你讲能够写出更好的、正确使用了提供的api的非阻塞的应用。

如果你对JavaScript这么语言还比较陌生,那么本文可以帮助你理解为甚JavaScript和其他语言相比这么“神奇”。

如果你已经是一名富有经验的JavaScript开发者,那我们希望本文能在你每天都在使用的JavaScript运行时到底如何工作的问题上提供一些新的见解。

JavaScript 引擎

JavaScript引擎的一个流行实例是谷歌的V8引擎,V8引擎运用在谷歌浏览器和node.js内部。这里有一个简化的视图:



引擎由主要由两部分组成:

* 内存堆—这里是内存分配发生的地方。

* 调用堆栈—这是当你的代码执行时堆栈帧的位置

运行时

浏览器中有许多api几乎每个JavaScript开发者都使用过(比如“setTimeout”),然而,这些api并不是引擎提供的。

那么,他们是哪儿来的呢?

事实上,情况有些复杂。


所以,我们不仅有引擎,还有更多东西。我们有浏览器提供的Web APIs,比如,DOM, AJAX, setTimeout 等等。

此外,还有如此受欢迎的事件循环和回调队列。

调用堆栈(The Call Stack)

JavaScript是一门单线程的编程语言,这意味着它只有一个堆栈(Call Stack),一次只能做一件事情。

堆栈是一种用来记录代码在程序中的位置的数据结构,当我们运行到一个函数调用时,我们把它放在堆栈的顶部,当函数运行完以后,我们把它从堆栈的顶部移除(出栈)。这就是堆栈所能做的全部了。

让我们来看个例子,看看下面的代码:


当引擎开始执行代码的时候,堆栈里面是空的。然后,会按下面这几个步骤运行:


每一次进栈被称为一个栈帧(Stack Frame)。

这正是抛出异常时堆栈跟踪的构造方式 — 它基本上是异常发生时调用堆栈的状态。看看下面的代码:


如果在谷歌浏览器中执行(假如是在一个叫foo.js的文件里),将会发生以下的堆栈跟踪:


“堆栈溢出(Blowing the stack)”—当达到堆栈调用最大容量时会发生这种情况。要发生这种情况也很简单,尤其是当你使用递归而没有广泛地测试你的代码的时候。看看下面的例子:

function foo(){
    foo()
};

foo();


当引擎开始执行这段代码的时候,它开始调用‘foo’函数,然而‘foo’是一个递归它以调用它本身开始,并且没有任何终止条件。所以,执行的每一步,这个函数会一遍又一遍地进栈。看起来就像这样:


但到了一定程度,函数调用的数量超出了堆栈本身的容量,浏览器就会采取行动,抛出一个错误。类似这样:


                    

在单线程中运行代码会非常简单,因为你不用处理一些发生在多线程环境里的复杂场景。比如:死锁(deadlocks)

但是在单线程中运行也有很多限制,由于JavaScript只有一个Call Stack,那么当事情变慢时会发生什么?

并发和事件循环

如果在调用堆栈中有函数调用需要花费大量时间才能处理,会发生什么?比如,想象一下你想用JavaScript在浏览器上做一些复杂的图片转换。

你或许会问—为什么这是一个问题?问题就是当堆栈里有函数需要执行的时候,浏览器实际上任何其他事都做不了—它被阻塞了。这就意味着浏览器不能渲染,不能执行任何其他代码,只能卡住了。如果你想要在你的应用中有流畅的UI,这就是个问题了。

这还不仅仅是唯一的问题。一旦浏览器要在堆栈中处理很多任务的时候,可能会很长一段时间没有响应。这时候大多数浏览器都会抛出错误,询问你是否要终止该网页。

                  

现在,这不是最好的用户体验,对吗?

所以,我们如何既能执行繁重的代码同时也不会阻塞UI渲染或导致浏览器不响应呢?well,解决办法就是异步回调。

这在“JavaScript如何工作”的第二部分教程“V8引擎内部+关于如何编写优化代码的5个技巧”(我的译文)中有更详细的解释。

同时,如果你很难再现和理解你的JavaScript应用中出现的问题,你可以看看SessionStack.

SessionStack记录了你的应用中发生的任何事情:所有DOM更改,用户交互,JavaScript异常,堆栈跟踪,失败的网络请求和调试消息。

使用SessionStack,你可以将网络应用中的问题作为视频重播,并查看用户发生的所有事情。

这儿有一个免费的方案,不需要任何的通行证。Get Start Now