[译] JavaScript的工作原理:引擎、运行时间以及调用堆栈的概述

283 阅读5分钟

JavaScript的工作原理:引擎、运行时间以及调用堆栈的概述

随着JavaScript的越来越流行,各个团队正在利用它在前端、后端、混合应用程序、嵌入式设备等等进行多层次支持。 这篇文章成为系列文章中的第一篇,旨在深入挖掘JavaScript及其实际的工作方式:通过了解JavaScript的组成部分以及它们如何一起发挥作用,您将能够编写出更好的代码和应用。在构建SessionStack时,我们还将分享一些经验法则。SessionStack是一种轻量级的JavaScript应用程序,必须强大且高性能才能保持竞争力。

根据GitHut统计数据显示,就GitHub中的Active Repository和Total Pushs而言,JavaScript排名第一。在其他类别中也没有落后太多。

(查看最新的GitHub语言的统计信息) 如果项目越来越依赖JavaScript,那么意味着开发人员必须利用语言和生态系统提供的所有内容,并且对内部结构越来越深入的理解才能开发出出色的软件。

事实证明,有很多开发人员每天都在JavaScript,但是不了解幕后情况。

概述

几乎每个人都听说过V8引擎是一个概念,而且大多数人都知道JavaScript是单线程的,或者它正在使用回调队列。 在这篇文章中,我们将详细介绍所有概念,并说明JavaScript是如何运行。通过了解这些详细信息,您将能够编写出更好、无障碍的应用程序,这些应用程序可以正确利用所提供的API。

如果您是JavaScript的新手,此文章将帮助您了解JavaScript与其他语言相比为何如此“古怪”。如果您是一个经验丰富的JavaScript的开发人员,希望它能为您提供每天实际工作中使用的JavaScript运行时间的新见解。

JavaScript引擎

Google的V8引擎是JavaScript引擎的一个流行的示例。例如,V8引擎用于Chrome和Node.js。这是非常简化的外观视图:

该引擎包括两个主要的组件:

*内部堆-这是发生内存分配的地方

*调用堆-这是代码执行时堆栈帧所在的位置

运行时间

浏览器中的API几乎已经被所有的JavaScript开发人员使用(例如setTimeout),但是引擎并不提供这些API。 那么,它们来自哪里?

事实证明,现实有点复杂。

因此,我们有引擎,但试试上还有更多。我们拥有由浏览器提供的称为Web API,例如DOM,AJZX, setTimeout等。 然后,我们有了非常流行的时间循环和回调队列。

调用堆栈

JavaScript是一种单线程编程语言,这意味着它具有单个调用堆栈。因此,它一次只能做一件事。

调用堆栈是一种数据机构,它基本上记录我们在程序中的位置。如果进入函数,则将其放在堆栈的顶部。如果我们从函数犯规,则会弹出堆栈顶部。这就是堆栈所能做的。

让我们来看一个例子,看下面这段代码:

function multiply(x, y) {
   return x * y;
}function printSquare(x) {
   var s = multiply(x, x);
   console.log(s);
}printSquare(5);

当引擎开始执行此代码时,调用堆栈将为空。之后将执行以下步骤:

调用堆栈中的每个条目都称为堆栈帧

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

function foo() {
   throw new Error('SessionStack will help you resolve crashes :)');
}function bar() {
   foo();
}function start() {
   bar();
}start();

如果在Chrome中执行此操作(假设此代码位于名为foo.js的文件中),则会生成一下堆栈跟踪:

“炸毁堆栈”—当您达到最大呼叫堆栈大小时,就会发生这种情况。这很容易发生,特别是如果你能使用递归而不进行大量测试的话。看一下以下示例代码:

function foo() {
    foo();
}foo();

当引擎开始执行此代码时,它首先调用函数“foo”。但是,此函数是递归的,并且在没有任何终止条件的情况下开始调用自身。因此,在执行的每个步骤中,都将相同的函数一遍又一遍地添加到调用堆栈中。看起来像这样:

但是在某些时候,“调用堆栈”中的函数调用的数量超出了“调用堆栈”的实际大小,浏览器决定通过抛出错误来采取措施,该错误看起来像:

在单线程上运行代码非常容易,因为不需要处理多线程环境中出现的复杂的情况—例如死锁。

但是在单线程上运行也有很大得限制。由于JavaScript具有单个调用堆栈,所以当变慢时会发生什么?

并发与时间循环

当在调用堆栈中进行函数调用要花费大量时间才能处理时,会发生什么情况呢?例如,假设您想在浏览器上使用JavaScript进行一些复杂的图像转换。

你可能会问—这为什么是问题?问题是,尽管调用堆栈具有要执行的功能,但浏览器实际上无法执行其他任何操作,它已被阻止。这意味着浏览器无法渲染,无法运行任何其他代码,只是卡住了。如果您想要在应用程序中使用流畅的用户界面,则会产生问题。

这不是唯一的问题。一旦您的浏览器开始处理“调用堆栈”中的许多人物,它可能会在很长一段时间内停止响应。而且大多数浏览器都会通过引发错误来采取行动,询问您是否要终止网页。

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

那么,如何在不阻塞UI并使浏览器无响应的情况下执行繁重的代码呢?好吧,解决方案是异步回调。

这将在“JavaScript实际工作原理”教程的第2部分中进行更详细的解释:“在V8引擎内部+5条有关编写优化代码的技巧”。