译者:Ar0nW
译文地址:https://ar0n.wang/2017/08/31/how-does-javascript-actually-work-part-1/
原文地址:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
由于JavaScript越来越受欢迎,许多开发团队也将其利用在许多层面上,前端,后端,混合型应用,嵌入式设备等等。
如Githutstats, JavaScript在github的活跃仓库和Push统计上名列前茅。在其他排行上,也没有落后于其他仓库的活跃程度。
![](https://img.haomeiwen.com/i7675083/5a8d98394b779c71.png)
如果项目较多地依赖于JavaScript,那么意味着开发人员必须更深入的了解内部机制,然后利用JavaScript语言的一切和生态系统去构建一个令人惊奇的应用。
事实证明,有很多的开发人员每天都在使用JavaScript,但并不知道底层做了哪些操作。
概述
大多数人已经知道V8引擎是一个什么样的概念,并且大多数人都知道JavaScript单线程的,或者说它使用了一个回调队列。
在这篇文章中,我们会详细讲解一下这些概念并解释JavaScript是如何运行的。通过了解这些细节,你将可以正确地利用提供的API写出更好的、非阻塞的应用。
如果你是JavaScript新手,这篇文章将会让你明白为什么JavaScript与其他语言对比起来有如此多的“怪异”特性。
如果你是个有经验的JavaScript开发人员,希望它会带给你一些关于JavaScript运行时的新看法。
JavaScript引擎
一个流行的JavaScript引擎的例子是google的V8引擎, 被应用在Chrome和Node.js的内部。这里有一个简化的例子:
![](https://img.haomeiwen.com/i7675083/b6f2ffa20e6215d8.png)
这个引擎由两个主要的组件构成:
内存堆 — 这是发生内存分配的地方
调用栈 — 这是你的代码在栈帧中执行的地方
运行时
有些API在浏览器中几乎被所有的开发人员使用过(例如:setTimeout)。然而这些API并不是引擎提供的。
所以,它们是从哪儿来的呢?
事实证明,现实是有点儿复杂的。
![](https://img.haomeiwen.com/i7675083/87085c608cb1bee0.png)
因此,实际上除了JavaScript引擎以外,还有其他的组件。其中有个组件就是由浏览器提供的,叫Web APIs,像DOM,AJAX,setTimeout等等。
然后还有就是非常受欢迎的事件循环和回调队列。
调用栈
JavaScript是单线程的编程语言,意味着它有一个单一的调用栈。因此它只能在同一时间做一件事情。
调用栈是一种数据结构,它基本上记录了我们在程序中的什么位置。如果我们步入一个函数中,我们会把这些数据放在堆栈的顶部。如果我们从一个函数中返回,这些数据将会从栈顶弹出。这就是堆栈的用途。
我们来看个例子:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
vars = multiply(x, x);
console.log(s);
}
printSquare(5);
当JavaScript引擎开始执行这段代码的时候,调用栈是空的。
之后将会执行如下的步骤:
![](https://img.haomeiwen.com/i7675083/b052ad3b2f3d789b.png)
调用栈中的每个条目叫做栈帧。
这就是当一个异常抛出时,堆栈跟踪是如何被构造的 — 当异常发生时,这基本上是 调用栈的状态。让我们来看如下的代码:
function foo() {
thrownewError('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
如果这段代码是在Chrome中执行(假设这段代码是在foo.js这个文件里面),以下的堆栈跟踪将会产生:
![](https://img.haomeiwen.com/i7675083/87f653113c2ba546.png)
“Blowing the stack”– 这一切发生在达到调用栈最大值的时候。这种情况可能很容易发生,尤其是当你使用递归并且没有全面地测试这段代码的时候。让我们来看下示例代码:
function foo() {
foo();
}
foo();
当引擎开始执行这段代码的时候,它首先调用foo()函数,然而,这个函数是递归的,并且调用自身而且没有任何终止条件。因此在每一次执行时,同样的函数将一次又一次地添加到调用栈上。这看起来像是这样的:
![](https://img.haomeiwen.com/i7675083/0a255d67428a991f.png)
在某个时刻,函数调用的数量会超过调用栈的大小,浏览器将会决定采取行动,通过抛出一个错误,看起来像这样:
![](https://img.haomeiwen.com/i7675083/3017ea15e7c7372a.png)
在单线程中运行代码可以变得很轻松,因为你不必处理在多线程环境中产生的复杂场景 — 例如,死锁。
但是在单线程上运行是很受限制的。因为JavaScript只有一个单一的调用栈,当它的运行变慢时发生了什么?
并发和事件循环
当你在调用栈上为了处理一个函数调用时花费了大量的时间会发生什么?举个例子,想象一下,你想要在浏览器中使用JavaScript去做一些复杂的图像变换。
你可能会问 — 这为什么是一个问题?问题是当调用栈上有函数在执行时,浏览器实际上还不能做其他事 — 因为它被阻塞了。这意味着浏览器不能做渲染,它不能运行任何其他的代码,它只是卡住了。如果你想要在你的应用中有一个流畅的界面,这就产生了问题。
而且这不是仅有的问题。一旦你的浏览器开始在调用栈上处理非常多的事务时,它可能会停止响应很长一段时间。和大多数浏览器一样会引发一个错误,问你是否要终止这个web页面。
![](https://img.haomeiwen.com/i7675083/b1461182a3bbd671.jpeg)
现在,这不是最好的用户体验,对吗?
因此, 我们怎样才能在执行大量代码的时候不会阻塞用户界面并造成浏览器的未响应?解决办法是异步回调。
这将在《JavaScript是如何工作的》第2部分中详细解释,请继续关注:)
在此期间,如果你正在为你的JavaScript Web应用重现和理解某些问题处于困难期,那就去SessionStack看一看。SessionStack会记录你的Web应用上的任何东西:所有DOM的变化,用户交互,JavaScript异常,堆栈追踪,失败的网络请求,和调试信息。
使用SessionStack,你可以重放在web应用中的问题,并且你将看到发生在你的用户那儿的所有问题。
有一个免费使用的计划允许你使用它。
网友评论