OverView
无意中搜到一个系列博客 --[How JS Works ](How JavaScript Works – SessionStack Blog),作者是一个叫SessionStack(这个产品没太看懂是做啥的,大概是一种非侵入式的框架,能够监控前端应用的数据,行为,异常等便于你优化应用)的产品的创始人和员工。
博客内容很好,分了两部分。
第一部分有19章,是2017~2018年更新的。这部分已经被翻译成了中文。[详见](GitHub - Troland/how-javascript-works: Knowledge about how javascript works, event loop,service worker,etc.)
第二部分有15章,是今年更新的。国内还没有翻译。
为什么要做整个系列
由于在学习一些小众的框架或者插件的时候,要阅读英文文档,要靠连蒙带猜来应付。
外文文档也是技术宅写的,文笔也很难说流畅,再加上一些计算机术语,读起来也不那么轻松。
因此,我打算自己先译一些文章,提升一下文档阅读能力。
前19章,我会自己翻译+参考大佬们的中文翻译,增加一些注解和删减。
后15章,将会独立翻译。
如果读者对运行时,调用栈没有概念,可能要先做一些功课。
本章推荐指数:4(满分5)
这是 JavaScript 工作原理的第一章。本章会对语言引擎,运行时,调用栈做一个概述。
概述
红宝书对JS的定义是,JS = ES+DOM+BOM。
这个定义指出了JS机制的范围,会包含JS引擎,HTML结构,CSS原理,ES等。
我们从V8开始,几乎所有人都已经听说过 V8 引擎的概念,并且很多人知道 JavaScript 是单线程的或者说是使用回调队列的。
本章会详细解释JS的这些概念和工作原理,了解这些细节,帮你跃入黄金段位,写出更流畅的代码。
JavaScript 引擎
Chrome 浏览器有一个内核,叫chromium。
chromium中有一个大型的模块,叫渲染引擎。
渲染引擎的责任就是解析HTML,计算CSS,以及执行JS。
其中执行JS的那一部分,就是V8引擎。
V8 引擎有两个常见的宿主环境,一个是Node,一个就是基于chromium的浏览器比如Chrome.
引擎包括两个主要组件:
- 动态内存管理 - 在这里分配内存
- 调用栈-这里代码执行即是你的堆栈结构
运行时
几乎每个 JavaScript 开发者都使用过一些浏览器 API(比如 setTimeout),要知道这个API并不是引擎所提供的。
那么它们从何而来?我们看一下图,这个图是浏览器的部分架构
可以看到,除了引擎但是实际上还有更多其它方面的东西。
有被称为 Web API 的东西,这些 Web API 是由浏览器提供的,比如 DOM,AJAX,setTimeout 以及其它。
以及流行的事件循环和回调队列。
调用栈
JavaScript 只是一个单线程的编程语言,这意味着它只有一个调用栈([什么是调用栈](Call stack(调用栈) - 术语表 | MDN (mozilla.org)))。这样它只能一次做一件事情。
调用栈是一种栈结构,里面会记录我们在程序中的大概位置。
当执行进入一个函数,调用栈把它置于栈的顶部。 如果函数调用结束,则从栈顶部移除该函数。 举个栗子。查看如下代码:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
当引擎开始执行这段代码的时候
首先调用栈会被清空
之后,产生如下步骤:
调用栈中的每个入口被称为StackFrame。
这正是当浏览器抛出异常的时候,栈追踪是如何被构建的-当发生异常的时候,栈追踪就是调用栈的状态。
如下代码:
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 来执行一些复杂的图像转化。
转换的函数会被放在调用栈上执行,此时,浏览器实际上不能做其它任何事-它被阻塞了。浏览器在执行JS的时候,是不能进行渲染的,也就是不能对UI进行更新。如果转换的函数执行太久,那么浏览器的页面就卡住了。
一旦浏览器开始在调用栈中执行这种任务,浏览器将会在相当一段时间内停止交互。大多数浏览器会抛出一个错误,询问你是否关闭网页。
那么,如何不阻塞 UI 且不让浏览器停止响应来执行运行缓慢的代码呢?使用异步回调。
这将会在 『JavaScript 工作原理』 第二章:『在V8 引擎中如何写最佳代码的 5 条小技巧』中进行详细阐述。
单词表
| embedded devices | 嵌入式设备 |
| utilize | 利用 |
| ecosystem | 生态系统 |
| internals | 内在,内部 |
| allocation | 分配(一般是内存分配) |
| recursion | 递归 |
| scenarios | 场景 |
| concurrency | 并发 |