JavaScript 引擎是什么?
JavaScript 引擎说起来最流行的当然是谷歌的 V8 引擎了, V8 引擎使用在 Chrome 以及 Node 中,下面有个简单的图能说明他们的关系:
这个引擎主要由两部分组成:
- 内存堆:这是内存分配发生的地方
- 调用栈:这是你的代码执行时的地方
引擎怎么运行?
所以说我们还有很多引擎之外的 API,我们把这些称为浏览器提供的 Web API,比如说 DOM、AJAX、setTimeout等等。
然后我们还拥有如此流行的事件循环和回调队列。
为什么要知道堆栈?
如果一个项目越来越依赖 JavaScript,这就意味着开发人员必须利用这些语言和生态系统提供更深层次的核心内容去构建一个令人振奋的应用。然而,事实证明,有很多的开发者每天都在使用 JavaScript,但是却不知道在底层 JavaScript 是怎么运作的。
什么是调用堆栈?
JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事。
一个调用堆栈 是一个解释的机制(如在Web浏览器中的JavaScript解释器)
调用栈是一种数据结构,它记录了我们在程序中的位置。如果我们运行到一个函数,它就会将其放置到栈顶。当从这个函数返回的时候,就会将这个函数从栈顶弹出,这就是调用栈做的事情。
- 当脚本调用函数时,解释器将其添加到调用堆栈,然后开始执行该函数。
- 该函数调用的任何函数都会进一步添加到调用堆栈中,并在到达其调用的位置运行。
- 当前函数完成后,解释器将其从堆栈中取出并在最后一个代码清单中从中断处继续执行。
- 如果堆栈占用的空间超过分配给它的空间,则会导致“堆栈溢出”错误。
(1)例一:
function greeting() {
// [1] Some codes here
sayHi();
// [2] Some codes here
}
function sayHi() {
return "Hi!";
}
// Invoke the `greeting` function
greeting();
// [3] Some codes here
-
忽略所有功能,直到它到达greeting()功能。
-
调用该greeting()功能。
-
将
greeting
函数添加到调用堆栈列表中。调用堆栈列表:
greeting -
执行
greeting
函数中的所有代码行。 -
开始使用该sayHi()功能。
-
将该sayHi()函数添加到调用堆栈列表中。
调用堆栈列表:
greeting
sayHi -
执行sayHi()函数内的所有代码行,直到结束。
-
将执行返回到调用的行sayHi()并继续执行greeting()函数的其余部分。
-
sayHi()从我们的调用堆栈列表中删除该函数。
调用堆栈列表:
greeting -
当greeting()函数内部的所有内容都被执行后,返回其调用行继续执行其余的JS代码。
-
greeting()从调用堆栈列表中删除该函数。
调用堆栈列表:
EMPTY
我们从一个空的调用堆栈开始,每当我们调用一个函数时,它会自动添加到调用堆栈中,在执行所有代码后,它会自动从调用堆栈中删除。最后,我们最终得到了一个空堆栈。
(2)例二:
什么是堆栈溢出?
“堆栈溢出”,当你达到调用栈最大的大小的时候就会发生这种情况,而且这相当容易发生,特别是在你写递归的时候却没有全方位的测试它。
例如:
function foo() {
foo();
}
foo();
当我们的引擎开始执行这段代码的时候,它从 foo 函数开始。然后这是个递归的函数,并且在没有任何的终止条件的情况下开始调用自己。因此,每执行一步,就会把这个相同的函数一次又一次地添加到调用堆栈中。然后它看起来就像是这样的:
在单个线程上运行代码很容易,因为你不必处理在多线程环境中出现的复杂场景——例如死锁。
什么是并发与事件循环?
调用栈中的函数调用需要大量的时间来处理,那么这会发生什么情况呢?例如,假设你想在浏览器中使用 JavaScript 进行一些复杂的图片转码。
事实上,问题是当调用栈有函数要执行,浏览器就不能做任何事,它会被堵塞住。这意味着浏览器不能渲染,不能运行其他的代码,它被卡住了。如果你想在应用里让 UI 很流畅的话,这就会产生问题。
而且这不是唯一的问题,一旦你的浏览器开始处理调用栈中的众多任务,它可能会停止响应相当长一段时间。大多数浏览器都会这么做,报一个错误,询问你是否想终止 web 页面。
什么是执行上下文?
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
执行上下文的类型?
全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。