Javascript语言现在变得越来越流行,各个团队利用了它在各种层面上的支持特性比如前端、后端
混合的应用程序、嵌入式设备等等。
这篇文章作为这一系列文章的第一篇,旨在深挖javascript的底层工作原理:我们认为通过了解javascript
的构建块以及了解它们是怎么一起工作的可以使得我们写出更好的代码和app。我们还将分享构建SessionStack时的一些经验法则,这是一个轻量级的JavaScript应用程序,它必须保持强大且高性能才能保持竞争力。
如GitHut统计中所示,JavaScript在GitHub中的活动存储库和总推送量方面位居前列。相比其他类别也不落后。如果项目越来越依赖于JavaScript,这意味着开发人员必须利用语言和生态系统提供的所有内容,深入了解内部,才能构建出令人惊叹的软件。
事实证明,有很多开发人员每天都在使用JavaScript,但并不知道底层会发生什么。
概览
几乎每个人都听说过V8引擎这个概念,大多数人都知道JavaScript是单线程的,或者它使用的是回调队列。在这篇文章中,我们将详细介绍所有这些概念,并解释JavaScript如何运行。通过了解这些细节,您将能够编写更好的非阻塞应用程序,并且学会正确使用所提供的API。
如果你对JavaScript比较陌生,那么这篇博文将帮助你理解为什么说JavaScript与其他语言相比是比较怪异的。
如果你是一位经验丰富的JavaScript开发人员,我希望能够提供一些关于你每天都在使用的JavaScript Runtime的实际工作情况的全新理解。
Javascript引擎
关于JS引擎一个最著名的例子就是谷歌的V8引擎。目前Chrome和Node.js中使用的就是V8引擎。下面是一个简单的视图:
引擎包含两个主要部件:
堆内存 - 这是发生内存分配的地方
调用栈 - 这是代码执行时的栈帧
运行时
几乎所有的JS开发者在浏览器中都使用过API(例如“setTimeout”)。但是,这些API不是由引擎提供的。
那么这些API从哪里来呢?
事实证明,现实情况有些复杂。
如图所示,可以发现其实我们除了引擎之外还依赖其他很多东西。像DOM、AJAX、setTimeout等等这些东西是由浏览器提供的,被称为Web API。
然后,我们就有了非常流行的事件循环和回调队列。
调用栈
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的文件中),那么会产生下面的堆栈跟踪:
“栈上溢(Blowing the stack)” - 当达到最大调用栈大小时就会发生这种情况。 这可能会很容易的发生,特别是使用递归的情况下。
看看这个示例代码:
function foo() {
foo();
}
foo();
当引擎开始执行这个代码时,它首先调用函数“foo”。
然而,这个函数是递归的,并且没有任何终止条件。 所以在执行的步骤中,同一个函数会一次又一次地添加到调用栈中。 它看起来像这样:
在某些情况下,调用栈中函数调用的数量超出了调用栈的实际大小,浏览器通过抛出一个错误(如下所示)来采取行动:
https://cdn-images-1.medium.com/max/800/1*e0nEd59RPKz9coyY8FX-uw.png
在单线程上运行代码可能会更容易一些,因为不必担心多线程环境中出现的复杂场景,例如死锁。
但是在单线程上运行也是非常有限的。 由于JS只有一个调用栈,所以当运行很慢时会发生什么?
并发和事件循环
如果在调用栈中的函数调用需要花费大量时间才能进行处理,会发生什么? 例如,假设你想在浏览器中使用JS进行一些复杂的图像转换。
你可能会奇怪 - 为什么这是个问题?
但是问题是,虽然调用栈有执行的功能,但此时浏览器实际上不能做其他的任何事情 - 它被阻塞了。 这意味着浏览器无法进行渲染,也不能运行任何其他代码,它只是卡住了。 如果你想在你的应用程序中实现流畅的UI,这就会产生问题。
并且这还不是唯一的问题。 一旦浏览器开始在调用栈中处理如此多的任务,它可能会停止响应相当长的时间。 而且大多数浏览器会通过抛出错误来应对,询问是否要终止网页。
所以这不是一个好的用户体验,是吗?
那么,我们如何执行大量代码而不会阻塞UI使浏览器无法响应呢?解决方案是异步回调。
这将在“JavaScript底层是如何工作的”教程的第2部分中更详细地解释。
同时,如果在JS应用遇到了难以复制和理解问题,请查看SessionStack。 SessionStack记录Web应用程序中的所有内容:所有的DOM更改,用户交互,JavaScript异常,堆栈跟踪,失败的网络请求和调试消息。
借助SessionStack,可以将视频中的问题重放为视频,并查看用户发生的所有事情。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)