原文 How JavaScript works: an overview of the engine, the runtime, and the call stackjavascript
随着 JavaScript
愈来愈流行,开发团队也更多地利用其来支持技术栈的各方面,前端、后端、混合应用、嵌入式设备等。前端
本文是旨在深刻挖掘 JavaScript
其工做原理系列教程的首篇:咱们认为经过了解 JavaScript
的构建单元并熟悉它们是怎样结合起来的,有助于你写出更好的代码和应用。咱们也会分享一些在构建 SessionStack
应用时用到的经验法则,为了维持其竞争力它是一个健壮、高性能的轻量级 JavaScript
应用。java
如GitHut stats所示,JavaScript
在活跃仓库数和GitHub
总推送数方面位于首位。在其余类别排名中落后的也很少。git
(查看最新的统计)。github
若是项目变得如此依赖 JavaScript
,这就意味着开发者必须更加深刻地理解其内部原理以充分利用语言和其生态提供的全部内容,从而构建更棒的软件。编程
事实显示,许多开发者天天都在使用 JavaScript
殊不知其底层发生了什么。后端
几乎每一个人都据说过 V8
引擎的概念,大多数人也知道 JavaScript
是单线程的或者使用回调队列。浏览器
在本文中,咱们会详细讲解这些概念并阐述 JavaScript
是如何运行的。经过了解这些细节,你就能够利用提供的 APIs
写出更好的、无阻塞的应用。session
若是你对 JavaScript
相对陌生,这个博客能够帮助你理解为什么与其余语言相比 JavaScript
如此怪异。数据结构
若是你是位经验丰富的 JavaScript
开发人员,也但愿能提供给你一些天天都在使用的 JavaScript
运行时实际运做机制的新看法。
JavaScript
引擎JS引擎的一个最流行的例子就是谷歌的 V8
。 V8
引擎使用在例如 Chrome
浏览器和 Node.js
中。下图是一个引擎组成部分的极简视图:
引擎由如下两个主要部分组成:
几乎全部 JavaScript
开发者都使用过浏览器提供的 APIs
(如 setTimeout
)。可是那些 APIs
并不禁引擎提供。
那么,它们来自哪里?
其实实际状况更加复杂一些。
因此,除了引擎以外实际上还有更多东西。咱们还有那些由浏览器提供 Web APIs
,如 DOM
、AJAX
、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工做原理》教程的第二部分详细解释。