原文地址:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
随着 JavaScript 变得越来越流行,各团队正在多个领域栈中使用它们,其中包括 — 前端,后端,混合应用,嵌入式等等。
这篇文章是系列中的第一篇,旨在深入挖掘 JavaScript 及其实际工作原理:通过了解 JavaScript 的构建块(building blocks of JavaScript)以及它们如何共同发挥作用,您将能够编写更好的代码和应用。我们还将分享我们在构建 SessionStack 时使用的一些经验法则,这是一个轻量级 JavaScript 应用程序,必须具有强大且高性能才能保持竞争力。
如 GitHut stats 统计显示,JavaScript 在活跃仓库数量以及提交数量上处于领先地位。其他方面也并不落后很多(截止到19年2季度,JavaScript 已在各个指标上领先于其他语言)。
可查看:https://madnight.github.io/githut/#/pull_requests/2019/2
如果项目越来越依赖于 JavaScript,这意味着开发人员必须利用语言和生态系统提供的内容,同时需要对内部进行更深入的了解,以便构建出色的应用。
事实证明,有很多开发人员每天都在使用 JavaScript,但却不了解幕后发生的事情(对JavaScript 是如何工作的原理却知之甚少)。
大多数人已经听说过 V8引擎的概念,我们知道 JavaScript 是单线程的,其使用的是回调队列(callback queue)。
这篇文章,我们将详细介绍这些概念以及 JavaScript 实际运行方式。通过了解这些细节,你将能够编写更加健壮,以及正确利用所提的API的非阻塞的应用程序。
如果您对 JavaScript 比较陌生(新手),这篇文章将帮助你理解为什么 JavaScript 与其他语言比起来是如此的“惊艳(weird)”。
如果您是一个经验丰富的 JavaScript 的开发者,希望它会给您带来一些关于您每天工作使用的 Javascript Runtime 的新见解。
Google’s V8 是流行的 JavaScript 引擎之一。V8 引擎用于 Chrome 和 Nodejs。这是一个简化版的视图:
引擎有两个重要组成部分:
有些浏览器 API 几乎所有 JavaScript 开发者人员都使用过(如,“setTimeout”),但是这些 API 并不是由引擎提供。
那么,他们来自哪里? 事实证明,它们来历有点复杂。
除了引擎,实际上还有很多其他东西。这些由浏览器提供的我们统称为 Web API,如 DOM, AJAX, setTimeout 等等。
接下来,我们将介绍一下非常流行的 事件循环(event loop) 和 回调队列(callback queue)。
JavaScript 是单线程编程语言,这意味着它只有一个Call Stack。因此它在某一时刻只能做一件事情。
调用栈(Call Stack)是一种数据结构,它主要是记录 JavaScript 整个执行过程。如果我们执行一个函数,我们将把它放在栈的顶部(压栈);如果函数返回,会弹出堆栈的顶部(出栈)。这一切都是堆栈可以做到的。
我们来看一个例子吧。看看下面的代码:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
当引擎开始执行此代码时,Call Stack 为空。 之后,步骤如下:
调用栈中的每个条目称为堆栈帧(Stack Frame)。
这正是抛出异常时堆栈跟踪的构造方式 - 它基本上是异常发生时调用栈的状态(异常后的全过程)。看看下面的代码:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
如果在Chrome中执行此操作(假设此代码位于名为foo.js的文件中),则将生成以下堆栈跟踪记录:
“堆栈溢出(Blowing the stack)” — 当达到最大调用堆栈大小时会发生这种情况(Javascript引擎产生的堆栈超过 Javascript 运行环境所提供的最大数量)。如果你使用没有设置结束条件的递归时,很容易产生。看看这个示例代码:
function foo() {
foo();
}
foo();
当引擎开始执行此代码时,它首先调用函数“foo”。但是,此函数是递归的,并且在没有任何终止条件的情况下开始调用自身(产生无限循环)。因此,在执行的每个步骤中,相同的函数会一遍又一遍地添加到调用堆栈中。它看起来像这样:
然而,在某些时候,调用堆栈中的函数调用数量超过了调用堆栈的实际大小,浏览器会抛出看起来像这样的错误:
在单个线程上运行代码非常简单,因为您不必处理多线程环境中出现的复杂场景 - 例如,死锁。 但是单线程运行也是受限的。由于 JavaScript 只有一个 Call Stack,what happens when things are slow?
如果在调用堆栈中有函数调用需要花费大量时间才能处理,会发生什么?例如,在浏览器中使用 JavaScript 进行一些复杂的图像转换。
你可能会问 - 这是问题吗?问题是,当 Call Stack 有待执行的函数时,浏览器实际上无法执行任何其他操作 - 它会被阻塞。这意味着浏览器无法渲染,无法运行任何其他代码,它被卡住了。如果您想在应用中使用流畅的UI,这会产生问题。
这不是唯一的问题。一旦 Call Stack 中等待执行的任务很多时,它可能会在相当长的时间内停止响应。大多数浏览器都会抛出一个提示信息,征求你您是否要关闭网页。
这样必然将导致非常差的用户体验。 那么,我们如何在不阻塞UI并使浏览器无响应的情况下执行繁重的代码呢好吧,这里我就不卖关子了,解决方案是异步回调(asynchronous callbacks)。