随着JavaScript(下文简称js)越来越流行,它在各个层面上都留下了身影:前端、后端、hybrid app、嵌入式设备等。
这篇文章是这个系列中第一个深入挖掘js是如何工作的:我们认为理解了js的底层建筑和运行方式可以使我们写出更好的代码和应用。
总览
应该很多人都听过V8引擎这个概念,也知道js是一个单线程的语言,还有它使用了回调队列。
这篇文章里我们会逐一解析每一个概念并解释js是怎么运行的。
如果你是js的初学者,那么这篇文章会让你了解为什么js比起其他语言会那么的“奇怪”。
如果你是js的高手,它会带给你一些关于你每天使用的js运行时是怎样工作的新颖知识。
JavaScript引擎
一个流行的js引擎是谷歌的V8引擎。它被使用在Chrome和Node.js中。这里简单地描述了他是什么样的:
引擎包含量两大部分:
- 内存堆(Memory Heap)——内存分配的地方
- 调用栈——这是你的代码执行时栈帧的位置
运行时
在浏览器中有很多被几乎每一位开发者使用的API(比如setTimeout
)。这些API,却并不是引擎提供的。
所以,他们来自哪里?
事实上要复杂一点点。
除了引擎以外还有一些东西。我们把这些浏览器提供的东西叫做Web API,比如DOM,AJAX,setTimeout等。
图下方是大名鼎鼎的事件循环(event loop)和回调队列( callback queue)。
调用堆栈(Call Stack)
Js是一个单线程的语言。所以它也只有一个调用栈。这意味着它同时只能做一件事件。
调用栈是一个数据结构,基本上它记录了我们的程序运行到哪了。如果我们运行进一个函数,那么我们把它放在堆栈的顶部。如果我们从一个函数返回,那么我们弹出(pop off)堆栈顶部的函数。这是堆栈所做的工作。
我们来看一个例子:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
复制代码
当我们的引擎刚开始执行上述代码的时候,调用栈会是空。之后的步骤会如下图所示:
调用栈中的每一条都称作栈帧(Stack Frame)
这也解释了当发生异常的时候堆栈轨迹( stack traces)是怎么被建立起来的——其实就是异常发生时调用栈的状态。看下面的列子:
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
复制代码
如果在Chrome中执行(假设运行的文件叫foo.js),会产生下面的堆栈轨迹:
"Blowing the stack"——这个异常发生在你达到了调用栈最大值的时候。这个很容易出现,特别是在你不小心错误地使用了递归的时候:
function foo() {
foo();
}
foo();
复制代码
当引擎开始执行代码,我们会无止尽地执行这个函数。所以这个函数被不断地堆在调用栈上面,就像这样:
这时候浏览器会爆出:
代码运行在单线程的环境是一个很轻松的事,你不必担心一些多线程带来的复杂场景——比如,死锁。
但是单线程也有一些限制。js只有一个调用栈,如何其中一些东西运行很慢怎么办?
并发 & 时间循环
当您在调用栈中行调用需要花费大量时间才能的函数时,会发生什么情况?比如你想在浏览器中进行图像处理。
你可能会问——这为什么会有问题?问题在于调用栈中有函数在执行,浏览器不能做其他的事情。这意味着浏览器不能渲染,不能跑其他代码。这会成为流畅UI界面的阻碍。
而且,一旦你的浏览器要处理太多的任务了,它会失去响应。一些浏览器会采取行动,询问你是否终止这个网页。
所以我们不卡死浏览器且拥有流畅UI的情况下执行大量代码呢?解决方案是异步调用。
这会在本系列的下一篇文章中提到:“Inside the V8 engine + 5 tips on how to write optimized code”。(译注:后续翻译尽请关注)
作者:Skandar-Ln
链接:https://juejin.im/post/5b346d416fb9a00e6a621cc4
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。