Skip to content

执行上下文

执行上下文可以理解为“JavaScript 在执行一段代码时,为这段代码准备的运行环境”。变量、函数声明、this 指向和外部作用域引用,都会放在这套环境里处理。

为什么需要执行上下文

JavaScript 在真正执行代码前,需要先回答几个问题:

  • 当前正在执行哪一段代码。
  • 这段代码里有哪些变量和函数。
  • this 应该指向谁。
  • 当前代码向外应该关联哪一个作用域。

执行上下文就是这套运行时描述。

三种常见执行上下文

全局执行上下文

脚本开始运行时最先创建的上下文。

js
const appName = 'docs'

function boot() {
  console.log(appName)
}

函数执行上下文

每次调用函数,都会创建一个新的函数执行上下文。

js
function sum(a, b) {
  return a + b
}

sum(1, 2)
sum(3, 4)

上面两次调用会创建两个不同的函数执行上下文。

eval 执行上下文

eval 也会形成自己的执行上下文,但通常不建议依赖它。

执行上下文里有什么

可以先从概念上理解为包含以下几部分:

  • 变量环境:处理 var 和函数声明。
  • 词法环境:处理 letconst、块级作用域等。
  • this 绑定:记录当前调用时的 this
  • 外部环境引用:指向外层作用域,用于变量查找。

创建阶段和执行阶段

创建阶段

进入上下文后,JavaScript 会先做准备工作:

  • 确定作用域链。
  • 处理函数声明。
  • 处理变量声明。
  • 确定 this

这也是“提升”现象产生的根源。

js
console.log(foo) // undefined
foo()

var foo = 1

function foo() {
  console.log('function foo')
}

从结果看,函数声明和 var 声明都在执行前进入了上下文,只是初始化方式不同。

执行阶段

准备完成后,代码才会逐行执行,完成赋值、表达式计算、函数调用等操作。

执行栈

多个执行上下文会按调用顺序进入调用栈。

js
function one() {
  two()
}

function two() {
  three()
}

function three() {
  console.log('done')
}

one()

执行顺序可以理解为:

  1. 进入全局执行上下文。
  2. 调用 one(),压入 one 上下文。
  3. 调用 two(),压入 two 上下文。
  4. 调用 three(),压入 three 上下文。
  5. three 执行结束后出栈,再依次返回。

和提升的关系

函数声明会整体提升

js
foo()

function foo() {
  console.log('ok')
}

var 只提升声明,不提升赋值

js
console.log(count) // undefined
var count = 1

letconst 也会进入上下文,但有暂时性死区

js
// console.log(name) // Cannot access 'name' before initialization
const name = 'vfan'

this 的关系

this 不是在定义函数时决定的,而是在函数执行上下文创建时根据调用方式确定的。

js
function show() {
  console.log(this)
}

const obj = { show }

show() // 浏览器中通常是 window
obj.show() // obj

理解顺序

学习时建议按这个顺序串起来:

  1. 作用域与作用域链。
  2. 执行上下文。
  3. 变量提升。
  4. this 绑定。
  5. 闭包。

这样很多分散知识点会连成一套模型。

基于 MIT 许可发布