JavaScript的工作原理:引擎,运行时和调用堆栈的概述

原文地址: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 是如何工作的原理却知之甚少)。

Overview

大多数人已经听说过 V8引擎的概念,我们知道 JavaScript 是单线程的,其使用的是回调队列(callback queue)。

这篇文章,我们将详细介绍这些概念以及 JavaScript 实际运行方式。通过了解这些细节,你将能够编写更加健壮,以及正确利用所提的API的非阻塞的应用程序。

如果您对 JavaScript 比较陌生(新手),这篇文章将帮助你理解为什么 JavaScript 与其他语言比起来是如此的“惊艳(weird)”。

如果您是一个经验丰富的 JavaScript 的开发者,希望它会给您带来一些关于您每天工作使用的 Javascript Runtime 的新见解。

The JavaScript Engine

Google’s V8 是流行的 JavaScript 引擎之一。V8 引擎用于 Chrome 和 Nodejs。这是一个简化版的视图:

引擎有两个重要组成部分:

  • Memory Heap  — 内存分配发生的地方
  • Call Stack  — 代码执行时堆栈帧(stack frames)的位置

The Runtime

有些浏览器 API 几乎所有 JavaScript 开发者人员都使用过(如,“setTimeout”),但是这些 API 并不是由引擎提供。

那么,他们来自哪里? 事实证明,它们来历有点复杂。

除了引擎,实际上还有很多其他东西。这些由浏览器提供的我们统称为 Web API,如 DOM, AJAX, setTimeout 等等。

接下来,我们将介绍一下非常流行的 事件循环(event loop)回调队列(callback queue)

The Call Stack

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?

Concurrency & the Event Loop

如果在调用堆栈中有函数调用需要花费大量时间才能处理,会发生什么?例如,在浏览器中使用 JavaScript 进行一些复杂的图像转换。

你可能会问 - 这是问题吗?问题是,当 Call Stack 有待执行的函数时,浏览器实际上无法执行任何其他操作 - 它会被阻塞。这意味着浏览器无法渲染,无法运行任何其他代码,它被卡住了。如果您想在应用中使用流畅的UI,这会产生问题。

这不是唯一的问题。一旦 Call Stack 中等待执行的任务很多时,它可能会在相当长的时间内停止响应。大多数浏览器都会抛出一个提示信息,征求你您是否要关闭网页。

这样必然将导致非常差的用户体验。 那么,我们如何在不阻塞UI并使浏览器无响应的情况下执行繁重的代码呢好吧,这里我就不卖关子了,解决方案是异步回调(asynchronous callbacks)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券