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
打印的效果倒是两个 foo2
。
刷过面试题的都晓得这是由于 JavaScript 引擎并不是一行一行地剖析和实行顺序,而是一段一段地剖析实行。当实行一段代码的时刻,会举行一个“准备事情”,比方第一个例子中的变量提拔,和第二个例子中的函数提拔。
然则本文真正想让人人思索的是:这个“一段一段”中的“段”终究是如何分别的呢?
终究JavaScript引擎碰到一段如何的代码时才会做“准备事情”呢?
可实行代码
这就要说到 JavaScript 的可实行代码(executable code)的范例有哪些了?
实在很简单,就三种,全局代码、函数代码、eval代码。
举个例子,当实行到一个函数的时刻,就会举行准备事情,这里的“准备事情”,让我们用个更专业一点的说法,就叫做”实行上下文(execution contexts)”。
实行上下文栈
接下来题目来了,我们写的函数多了去了,如何治理建立的那么多实行上下文呢?
所以 JavaScript 引擎建立了实行上下文栈(Execution context stack,ECS)来治理实行上下文
为了模仿实行上下文栈的行动,让我们定义实行上下文栈是一个数组:
ECStack = [];
试想当 JavaScript 最早要诠释实行代码的时刻,最早碰到的就是全局代码,所以初始化的时刻起首就会向实行上下文栈压入一个全局实行上下文,我们用 globalContext 示意它,而且只有当全部应用顺序终了的时刻,ECStack 才会被清空,所以 ECStack 最底部永久有个 globalContext:
ECStack = [
globalContext
];
如今 JavaScript 碰到下面的这段代码了:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
当实行一个函数的时刻,就会建立一个实行上下文,而且压入实行上下文栈,当函数实行终了的时刻,就会将函数的实行上下文从栈中弹出。晓得了如许的事情道理,让我们来看看如何处置惩罚上面这段代码:
// 伪代码
// fun1()
ECStack.push(<fun1> functionContext);
// fun1中居然调用了fun2,还要建立fun2的实行上下文
ECStack.push(<fun2> functionContext);
// 擦,fun2还调用了fun3!
ECStack.push(<fun3> functionContext);
// fun3实行终了
ECStack.pop();
// fun2实行终了
ECStack.pop();
// fun1实行终了
ECStack.pop();
// javascript接着实行下面的代码,然则ECStack底层永久有个globalContext
解答思索题
好啦,如今我们已了解了实行上下文栈是如何处置惩罚实行上下文的,所以让我们看看上篇文章《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();
是否是有些差别呢?
固然了,如许归纳综合的回复实行上下文栈的变化差别,是否是依旧有一种意犹未尽的觉得呢,为了更细致解说两个函数实行上的区分,我们须要探讨一下实行上下文终究包含了哪些内容,所以迎接浏览下一篇《JavaScript深切之变量对象》。
下一篇文章
深切系列
JavaScript深切系列目次地点:https://github.com/mqyqingfeng/Blog。
JavaScript深切系列估计写十五篇摆布,旨在帮人人捋顺JavaScript底层学问,重点解说如原型、作用域、实行上下文、变量对象、this、闭包、按值通报、call、apply、bind、new、继续等难点观点。
假如有毛病或许不严谨的处所,请务必赋予斧正,非常谢谢。假如喜好或许有所启示,迎接star,对作者也是一种勉励。