平常在群里讨论一些概念性的问题,好比变量提高,做用域和闭包相关问题的时候,常常会听一些大佬们给别人解释的时候说执行上下文,调用上下文巴拉巴拉,总有点似懂非懂,不明觉厉的感受。今天,就对这两个概念梳理一下,加深对js基础核心的理解。javascript
当js引擎遇到这三种类型的代码的时候,都会进行一些准备工做,这些准备工做,专业的说法就叫执行上下文。或者说js引擎遇到这三种类型的代码的时候,就会进入到一个执行上下文。html
简而言之,执行上下文是评估和执行javascript代码的环境的抽象概念。每当javascript代码在运行的时候,它都是在执行上下文中运行。执行上下文能够理解为当前代码的执行环境,它会造成一个做用域(╭(╯^╰)╮,做用域就做用域嘛,说得这么拗口,非要搞个什么执行上下文的概念)。java
其实不必刻意去区分可执行代码与执行上下文。我的理解,当别人跟你聊一些概念性的东西,聊到可执行代码,可执行上下文,执行环境的时候,其实他们多是想说做用域,只不过表述方式不一样罢了。数组
以前写的这个,有点问题,惭愧。做用域与执行上下文是彻底不一样的两个概念。浏览器
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码编译成可执行代码,这个阶段做用域规则会肯定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段建立。闭包
在一个javascript程序中,一定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈(不少文章可能会称它为执行栈,执行上下文堆栈,函数调用栈,其实都是差很少的意思)。函数
关于栈的概念和特性在上一篇博客:js基础梳理-内存空间已有介绍。this
为了模拟执行上下文栈的行为,能够把它定义为一个数组:code
ECStack = [];
如今 javascript遇到下面这段代码了htm
let a = 'hello world'; function first () { console.log('进入 first 函数执行上下文'); second(); console.log('再次进入 first 函数执行上下文'); } function second () { console.log('进入 second 函数执行上下文'); } first(); console.log('进入 全局执行上下文(Global Execution Context)')
当上述代码在浏览器加载时,Javascipt引擎建立了一个全局执行上下文并把它压入了执行上下文栈,用 globalContext表示它,而且只有当整个应用程序结束的时候(浏览器关闭),ECStack才会被清空,因此程序结束以前,ECStack最底部永远有个 globalContext:
ECStack = [ globalContext ];
当执行到一个函数的时候,就会建立一个执行上下文,而且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了这样的工做原理,就能够分析出 ECStack的变化过程:
// 伪代码 // first() ECStack.push(<first> functionContext); // first中调用了second,继续建立second的执行上下文 ECStack.push(<second> functionContext); // second执行完毕 ECStack.pop(); // first执行完毕 ECStack.pop(); // javascript接着执行下面的代码,可是ECStack底层永远有个globalContext;
注意:函数中,遇到return能终止可执行代码的执行,所以会直接将当前上下文弹出栈。
例如,看如下这个闭包例子:
function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
由于f1中的函数f2在f1的可执行代码中,并无被调用执行,所以执行f1时,f2不会建立新的上下文,而直到result执行时,才建立了一个新的。具体演变过程以下:
// 伪代码: // 全局上下文入栈: ECStack = [ globalContext ]; // f1 EC入栈: ECStack.push(<f1> functionContext); // f1 EC出栈: ECStack.pop(); // result EC入栈: ECStack.push(<result> functionContext); // result EC出栈: ECStack.pop();
在接下来的文章中将梳理建立阶段的这三个步骤。