注:本文翻译自网上的文章,原文地址:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
随着JavaScript越来越流行,团队在多个层面对其进行了支持 - 前端、后端、混合应用程序、嵌入式设备等等。
本文是系列文章的第一篇,旨在深入研究JavaScript及其工作方式:我们认为通过了解JavaScript的构建块以及它们如何工作,您将能够编写更好的代码和应用。
如GitHut统计中所示,JavaScript在GitHub中的活动代码库和总推送量方面位居前列,在其他类别也不落后。
如果项目越来越依赖于JavaScript,这意味着开发人员必须利用语言和生态系统提供的所有内容,深入了解内部,从而构建出令人惊叹的软件。
事实证明,有很多开发人员每天都在使用JavaScript,但并不知道底层会发生什么。
概述
几乎每个人都已经听说过V8引擎这个概念,大多数人都知道JavaScript是单线程的,或者它使用回调队列。
在这篇文章中,我们将详细介绍所有这些概念,并解释JavaScript是如何运行的。 通过了解这些细节,您将能够编写更好的、非阻塞的应用程序,正确利用所提供的API。
如果您对JavaScript比较陌生,这篇文章将帮助您理解为什么JavaScript与其他语言相比比较“怪异”。
如果您是一位经验丰富的JavaScript开发人员,希望这篇文章能够为您提供一些对于JavaScript运行时是如何工作的全新见解。
JavaScript引擎
一个流行的JavaScript引擎的例子是Google的V8引擎。 例如,在Chrome和Node.js中使用的就是V8引擎。 下面是一个简化了的V8视图:
引擎由两个主要组件组成:
- 内存堆 - 这是内存分配发生的地方
- 调用堆栈 - 这是代码执行时的堆栈帧
运行时
浏览器中有几乎所有的JavaScript开发者都使用过API(例如“setTimeout”)。 但是,这些API不是由引擎提供的。
那么,他们从哪里来?
现实世界有点复杂。
所以,除了引擎,实际上还有更多,例如那些被浏览器提供的称为Web API的东西,如DOM,AJAX,setTimeout等等。
此外,我们还有使用广泛的事件循环和回调队列。
调用栈
JavaScript是一种单线程编程语言,这意味着它只有一个调用栈。 因此,它一次只可以做一件事。
调用栈是一个数据结构,它记录了程序执行到哪个地方。 如果进入一个函数,它就放在栈顶。 如果从一个函数返回,会从栈顶弹出,如同所有的堆栈所做的。
我们来看一个例子。 看看下面的代码:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
当引擎开始执行这个代码时,调用栈是空的。 之后,步骤如下:
调用堆栈中的每个条目称为堆栈帧。
这正说明了抛出异常时堆栈如何构建的 - 这基本上是异常发生时的调用堆栈的状态。 看看下面的代码:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果这段代码在Chrome中执行(假设这个代码在一个名为foo.js的文件中),那么会产生下面的调用栈:
“堆栈溢出” - 当达到最大调用堆栈大小时会发生这种情况。 这会很容易发生,特别是如果您使用递归且没有广泛地测试代码的时候。 看看这个示例代码:
function foo() {
foo();
}
foo();
- 1
- 2
- 3
- 4
当引擎开始执行这个代码时,它首先调用函数“foo”。 然而,这个函数是递归的,并且调用它自己而没有任何终止条件。 所以在执行的每个步骤中,同一个函数会一次又一次地添加到调用堆栈中。 它看起来像这样:
然而,在某些情况下,调用堆栈中某个函数调用的数量超出了调用堆栈的实际大小,浏览器通过抛出一个错误(如下所示)来决定如何处理:
在单线程上运行代码可能非常容易,因为您不必处理多线程环境中出现的复杂场景,例如死锁。
但是在单线程上运行也有局限。 由于JavaScript只有一个调用栈,当某个事务处理很慢时会发生什么?
并发 & 事件循环
如果在调用堆栈中进行函数调用需要花费大量时间,会发生什么? 例如,在浏览器中使用JavaScript进行一些复杂的图像转换。
你可能会问 - 为什么这是一个问题? 问题是,虽然调用堆栈在执行函数,但浏览器实际上不能做任何其它的事情 - 它被阻塞了。 这意味着浏览器无法渲染,也不能运行任何其他代码,它只是卡住了。 如果你想在应用程序中有着流畅的UI,这会产生问题。
这不是唯一的问题。 一旦您的浏览器开始在调用堆栈中处理如此多的任务,它可能会停止响应相当长的时间。 而且大多数浏览器通过引发错误来采取行动,询问您是否要终止网页。
现在,这个用户体验很糟糕,不是吗?
那么,我们如何执行密集运算代码而不会阻塞UI并使浏览器无法响应? 解决方案是异步回调。
这将在“JavaScript是如何工作的”教程的第2部分中更详细地阐述:“V8引擎内部+关于如何编写优化代码的5个技巧”。