作用域、作用域链、执行上下文、执行栈

前言

因为笔者在准备前端面试的过程中,对于作用域、作用域链、执行上下文、执行栈的概念时长感到力不从心、模糊不清,因此记录了这篇文章,深入探究以下。

作用域

说起作用域,其实分为两种:静态作用域动态作用域,js是前者。
所谓静态作用域,无非就是在编译阶段就产生的。学过编译原理的朋友都知道,在此法分析解阶段,编译器会将你的代码解析成一个个token关键字,形成AST抽象词法树,静态作用域在此时就可创建完成,而动态作用域只有在函数执行时才确认,js中的this很像动态作用域的概念,玩家可以通过bind、call等方法动态改变this的指向(箭头函数除外,箭头函数本身没有this,其中的this经过babel翻译之后只不过是一个被赋值了外层this值的一个变量而已,在定义时就已经确定),这也变相弥补了js是静态作用域的缺陷。
也就是说作用域定义了一套代码执行时变量的一个访问规则

js的作用域分为:

  • 全局作用域
  • 局部作用域(es6 let/const)

作用域链

为什么会有作用域链呢?js引入作用域链并非是多此一举(这里不要与js的原型链弄混),主要是为了提高js语言的容错率,在本层作用域未访问到的方法,会向上查找外层的作用域。知道找到全局作用域

举一个栗子:

1
2
3
4
5
6
7
8
9
const a = 1
function A() {
console.log(a); // 1
function C() {
console.log(a); // 1
}
C()
}
A()

执行A函数,A函数中找不到关于a的声明,那么A就会沿着作用域链向上查找,知道找到变量a。

执行上下文

执行上下文与作用域不同,执行上下文是在代码运行的过程中创建的一个变量对象,这个对象我们无法直接访问,但是可以访问其中的变量、this对象等。
作用域是在函数声明的时候就确定的一套变量访问规则,而执行上下文时函数执行时才产生的一系列变量的环境,也就是说作用域定义了执行上下文中的变量访问规则(这句话很重要),执行上下文在这个作用域规则的前提下进行变量查找,函数引用等具体操作。

执行上下文分为两个阶段:

  • 创建阶段
    为了方便理解,可以将作用域和this指向当作执行上下文的组成部分。
  • 执行阶段

执行栈

执行栈,在其他编程语言中也被称为“调用栈”,是一个具有后进先出结构的栈,它用于存储代码执行期间创建的所有执行上下文。
执行栈是存放执行上下文的一个栈,当新的函数执行时,那么该函数的执行上下文便会入栈,执行结束之后便会出栈。