likes
comments
collection
share

JS:执行上下文与作用域、作用域链的关系

作者站长头像
站长
· 阅读数 31

关于执行上下文与作用域的概念网络上的解释多且杂,涉及太多理解起来反而模糊,本文就用最容易理解的方式来讲讲执行上下文与作用域的概念,以及作用域链是怎么产生的。

执行上下文:

代码产生于编译阶段的,用于描述代码执行环境

执行上下文的类型:

执行上下文分为三种类型,分别是全局执行上下文(Global Execution Context)函数执行上下文(Function Execution Context)Eval 函数执行上下文(Eval Function Execution Context)

  • 全局执行上下文:只有一个。它创建了一个全局对象(浏览器中是window对象),并将this指向该对象。

  • 函数执行上下文:无数个。每次调用函数时,都会为该函数创建一个新的执行上下文。

  • eval函数执行上下文:运行在 eval 函数中的代码也会获得自己的执行上下文。

执行上下文的组成:

执行上下文有两个组成成分,分别为变量环境(VariableEnvironment)词法环境(LexicalEnvironment)

  • 变量环境:var声明的变量。

  • 词法环境:letconst声明的变量。

完整的执行上下文如下图所示:

JS:执行上下文与作用域、作用域链的关系

作用域与作用域链:

作用域:

函数身上的属性 [[scope]],用于存储函数中的有效标识符。

作用域链:

  • 作用域是执行期上下文对象的集合,这种集合呈链式连接,我们把这种链关系称之为作用域链。

  • 并不是在调用栈中从上往下查找,而是当前执行上下文变量环境中的 outer 指向来定,而 outer 指向的规则是,我的词法作用域在哪里,outer 就指向哪里。

  • 词法作用域:在函数定义时所在的作用域。

JS中代码运行图解:

以下将由一段代码的执行展示执行上下文与作用域链的关系。

function bar(){
    console.log(myName);
}

function foo(){
    var myName = '小红';
    bar();
}

var myName = '小明';

foo();

这段代码最终会输出什么呢?到底是小明还是小红?这就涉及到作用域链的指向问题。

第一步:预编译产生全局执行上下文,由于bar函数与foo函数声明在全局,因此是属于全局的有效标识符,同时全局作用域已经没有外层了,因此outer属性为null

JS:执行上下文与作用域、作用域链的关系

第二步:调用foo函数,foo的全局执行上下文入栈,由于myname声明在函数foo中,因此是属于foo作用域的有效标识符,同时,由于函数foo声明在全局,因此foo执行上下文中的outer指向全局,表明foo的外层作用域是全局。

形成 foo作用域 --> 全局作用域 的作用域链。

JS:执行上下文与作用域、作用域链的关系

第三步:调用bar函数,bar函数的全局执行上下文入栈,由于函数bar声明在全局,因此bar执行上下文中的outer指向全局,表明bar的外层作用域是全局。

形成 bar作用域-->全局作用域 的作用域链。

JS:执行上下文与作用域、作用域链的关系

由于调用函数bar执行上下文的outer指向全局,因此最后的输出为“小明”。

总结:

执行上下文和作用域分别为JavaScript执行机制中两个不同的概念。身为函数的属性,作用域在函数定义时就已经确定,而执行上下文则分为预编译产生的全局执行上下文被调用时产生的函数执行上下文eval 函数执行上下文,同时,在执行上下文的变量环境outer的指向产生了作用域链,同时outer的指向又由它的词法作用域来决定。

转载自:https://juejin.cn/post/7383561807178842112
评论
请登录