译者: 波比小金刚javascript
翻译水平有限,若有错误请指出。前端
ps: 最近开始整理全部的优质文章翻译集,固然若是你有好的文章请提 issue,我会找时间翻译出来。git
JavaScript 愈来愈流行,在前端、后端、hybrid apps、嵌入式设备开发等方向上都有它活跃的身影。后端
这篇文章是 How JavaScript Works 系列的开篇,该系列的文章旨在深刻挖掘 JavaScript 及其实际的工做原理。咱们认为了解 JavaScript 的构建块及其共同做用,能够帮助咱们写出更优雅、更高效的代码和应用。浏览器
正如 GitHut stats 所展现的同样,JavaScript 各方面的统计数据都是棒棒哒,顶多也就在个别统计项上落后了其余语言那么一丢丢。session
若是项目深度依赖 JavaScript,这意味着开发者须要对底层有极其深刻的了解,并利用语言和生态提供的一切东西来构建出色的应用。数据结构
然而,事实上,不少开发者虽然天天都在使用 JavaScript,却对其背后发生的事情一无所知。多线程
几乎每一个人都据说过 V8 引擎的概念,大多数人也都知道 JavaScript 是一门单线程语言或者知道它是基于回调队列的。并发
在这篇文章中,咱们将详细的介绍这些概念而且解释 JavaScript 实际的运行方式,经过对这些细节的了解,你能够写出更好、无阻塞的应用。
若是你是一名 JavaScript 新手,这篇文章将帮助你理解为何 JavaScript 和其它语言对比起来显得那么"奇怪"。
若是你是一名老司机,但愿可以为你带来一些对 JavaScript 运行时的新思考。
提及 JavaScript 引擎,不得不提的就是 Google 的 V8 引擎,Chrome 和 Nodejs 内部也是使用的 V8。这里有一个简单的视图:
引擎主要包含两个组件:
几乎全部的开发者都使用过浏览器中的 APIs (好比: setTimeout),然而,引擎并不提供这些 API。
那么,这些 API 从何而来?
事实上,这是一个很复杂的问题。
因此,除了引擎以外还有不少内容,包括咱们调用的浏览器提供的 Web APIs,好比:DOM, AJAX, setTimeout 等。
而后,还有大名鼎鼎的事件循环和回调队列。
JavaScript 是一门单线程语言,只有一个 Call Stack,所以一次也就能作一件事。
Call Stack 是一种数据结构,记录程序的位置。若是咱们进入函数,就把它放在堆栈的顶部,若是咱们从函数返回,就将其从堆栈顶部弹出。
咱们看一个例子:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
复制代码
引擎开始执行这段代码的时候,调用栈是空的,接着的步骤以下:
对于调用栈中的每个条目,咱们叫作"栈帧"(Stack Frame)
这正是异常抛出时堆栈追踪的构造方式 - 基本上就是异常发生时调用栈的状态。
咱们看看以下代码:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
复制代码
在浏览器执行(假设代码在 foo.js 文件),能够在控制台看到以下堆栈追踪信息:
"爆栈" - 当达到调用栈的最大大小的时候发生。并且这很容易发生,好比下面的这段牛逼的递归调用代码:
function foo() {
foo();
}
foo();
复制代码
当引擎开始执行这段代码的时候,首先调用函数 "foo",可是这个函数接着递归的调用本身,而且没有终止条件。相同的函数不断的加到调用栈中,以下:
当调用栈中函数的数量超过其阀值的时候,浏览器决定动手了。浏览器会抛出一个以下的异常信息!
单线程上运行一个程序,对比在多线程环境下的运行简单不少,由于不须要处理多线程运行下的一些复杂场景,好比:死锁。
可是单线程也会很坑的,既然只有一个调用栈,那么执行一个很慢很慢的计算的时候,你就会崩溃了。
若是你的调用栈中存在一个须要大量时间处理的函数的时候,会发生什么?假如你想在浏览器端经过 JavaScript 进行复杂的图像转换。
你可能会问 - 这也算是一个问题?问题就是当调用栈有函数在执行的时候,浏览器实际上不能作别的任何操做 - 它会被阻止。 这意味着浏览器不能渲染,不能执行别的代码,它被卡住了。若是你须要流畅的 UI 体验,那就很糟糕了。
这还不是惟一的问题,一旦浏览器遇到不少不少的任务须要在调用栈中处理的时候,可能很长的一段时间内会中止响应。这个时候大多数浏览器就会采起行动,问你是否须要终止网页。
这并非最好的用户体验,是吧?
因此,咱们如何处理繁重的代码并且不阻塞渲染或者不使浏览器中止响应呢,答案就是异步回调。
这将在本系列文章的第二部分详细阐述。