开篇
作为一个JavaScript的递次开发者,假如被问到JavaScript代码的实行递次,你脑海中是否是有一个直观的印象 — JavaScript 是递次实行的,可现实真的是如许的吗?
让我们首先看两个小例子:
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
刷过面试问题标都晓得:
JavaScript引擎并不是一行一行地剖析和实行递次,而是
一段一段地剖析实行,当实行一段代码的时刻,会举行一个
预备事情。
比方我们熟习的JavaScript中的变量提拔比方函数提拔都是在这个预备阶段完成的。
本文我们就来深切的研究一下,这一段一段中的段是如何分别的呢?
终究JavaScript引擎碰到一段如何的代码才会做”预备事情”呢?为相识答这个问题我们引入一个观点——实行上下文。
实行上下文
假如你做过小学的浏览明白,一定见到过如许的问题:联络上下文诠释句子,这里的上下文指的多是这个句子地点的段落,也多是这个句子地点段落的邻近段落。实际上,这里形貌的是一个句子的语境和作用局限,联络类比到递次中我们可以作以下定义:
实行上下文是当前JavaScript代码被剖析和实行时地点环境的抽象观点。
实行上下文的范例
实行上下文统共分为三种范例,有时刻我们也叫做可实行代码(executable code)
- 全局实行上下文: 只要一个,浏览器中的全局对象就是
window
对象,this
指向这个全局对象。 - 函数实行上下文: 存在无数个,只要在函数被挪用的时刻才会被建立,每次挪用函数都邑建立一个新的实行上下文。
-
Eval
函数实行上下文: 指的是运转在eval
函数中的代码,很罕用而且不发起运用。
举个例子,当实行到一个函数的时刻,就会举行预备事情,这里的”预备事情”,让我们用个更专业一点的说法,就叫做”实行上下文(execution context)“。
实行栈
接下来问题来了,我们写的函数多了去了,如何治理建立的那么多实行上下文呢?所以 JavaScript 引擎建立了实行上下文栈(Execution context stack )ECStack 来治理实行上下文。
这里我们可以简朴的以为 ECStack 是一个数组,相似如许:
ECStack = [];
实行栈,也叫做挪用栈,具有 LIFO(last in first out 后进先出) 构造,用于存储在代码实行时期建立的一切实行上下文。
- 初次运转JavaScript代码的时刻,会建立一个全局实行的上下文并Push到当前的实行栈中,每当发作函数挪用,引擎都邑为该函数建立一个新的函数实行上下文并Push当前实行栈的栈顶。
- 当栈顶的函数运转完成后,其对应的函数实行上下文将会从实行栈中Pop出,上下文的掌握权将移动到当前实行栈的下一个实行上下文。
让我们看一段代码来明白这个历程:
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
- 当上述代码在浏览器加载时,JavaScript引擎建立了一个全局实行上下文并把它压入(push) 当前的实行栈。当碰到 first() 函数挪用时,JavaScript引擎为该函数建立一个新的实行上下文并把它压入当前实行栈的顶部。
- 当从 first() 函数内部挪用 second() 函数时,JavaScript引擎为 second() 函数建立了一个新的实行上下文并把它压入当前实行栈的顶部,当 second() 函数实行终了,它的实行上下文会从当前栈弹出(pop),而且掌握流程抵达下一个实行上下文,即 first() 函数的实行上下文。
- 当 first() 实行终了,它的实行上下文从栈中弹出,掌握流程抵达了全局实行上下文。一旦一切的代码实行终了,JavaScript引擎从当前栈中移出全局实行上下文。
下面这张图,可以越发清楚的诠释上面这个实行历程
看两个思考题
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
两段代码实行的效果一样,然则两段代码终究有哪些差别呢?
答案就是实行上下文栈的变化不一样。
让我们模仿第一段代码:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
让我们模仿第二段代码:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
为了更细致解说两个函数实行上的区分,我们须要探讨一下实行上下文终究包含了哪些内容,我们须要越发深切相识变量对象的相关内容。