JS:执行上下文与作用域、作用域链的关系
关于执行上下文与作用域的概念网络上的解释多且杂,涉及太多理解起来反而模糊,本文就用最容易理解的方式来讲讲执行上下文与作用域的概念,以及作用域链是怎么产生的。
执行上下文:
代码产生于编译阶段的,用于描述代码执行的环境。
执行上下文的类型:
执行上下文分为三种类型,分别是全局执行上下文(Global Execution Context),函数执行上下文(Function Execution Context),Eval 函数执行上下文(Eval Function Execution Context)。
-
全局执行上下文:只有一个。它创建了一个全局对象(浏览器中是window对象),并将
this
指向该对象。 -
函数执行上下文:无数个。每次调用函数时,都会为该函数创建一个新的执行上下文。
-
eval
函数执行上下文:运行在eval
函数中的代码也会获得自己的执行上下文。
执行上下文的组成:
执行上下文有两个组成成分,分别为变量环境(VariableEnvironment)、词法环境(LexicalEnvironment)。
-
变量环境:
var
声明的变量。 -
词法环境:
let
、const
声明的变量。
完整的执行上下文如下图所示:

作用域与作用域链:
作用域:
函数身上的属性 [[scope]]
,用于存储函数中的有效标识符。
作用域链:
-
作用域是执行期上下文对象的集合,这种集合呈链式连接,我们把这种链关系称之为作用域链。
-
并不是在调用栈中从上往下查找,而是当前执行上下文变量环境中的
outer
指向来定,而outer
指向的规则是,我的词法作用域在哪里,outer
就指向哪里。 -
词法作用域:在函数定义时所在的作用域。
JS中代码运行图解:
以下将由一段代码的执行展示执行上下文与作用域链的关系。
function bar(){
console.log(myName);
}
function foo(){
var myName = '小红';
bar();
}
var myName = '小明';
foo();
这段代码最终会输出什么呢?到底是小明还是小红?这就涉及到作用域链的指向问题。
第一步:预编译产生全局执行上下文,由于bar
函数与foo
函数声明在全局,因此是属于全局的有效标识符,同时全局作用域已经没有外层了,因此outer
属性为null
。
第二步:调用foo
函数,foo
的全局执行上下文入栈,由于myname
声明在函数foo
中,因此是属于foo
作用域的有效标识符,同时,由于函数foo
声明在全局,因此foo
执行上下文中的outer
指向全局,表明foo
的外层作用域是全局。
形成 foo
作用域 --> 全局作用域 的作用域链。
第三步:调用bar
函数,bar
函数的全局执行上下文入栈,由于函数bar
声明在全局,因此bar
执行上下文中的outer
指向全局,表明bar
的外层作用域是全局。
形成 bar
作用域-->全局作用域 的作用域链。
由于调用函数bar
执行上下文的outer
指向全局,因此最后的输出为“小明”。
总结:
执行上下文和作用域分别为JavaScript执行机制中两个不同的概念。身为函数的属性,作用域在函数定义时就已经确定,而执行上下文则分为预编译产生的全局执行上下文与被调用时产生的函数执行上下文与eval 函数执行上下文,同时,在执行上下文的变量环境中outer
的指向产生了作用域链,同时outer
的指向又由它的词法作用域来决定。
转载自:https://juejin.cn/post/7383561807178842112