主题
执行上下文
执行上下文可以理解为“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和函数声明。 - 词法环境:处理
let、const、块级作用域等。 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()执行顺序可以理解为:
- 进入全局执行上下文。
- 调用
one(),压入one上下文。 - 调用
two(),压入two上下文。 - 调用
three(),压入three上下文。 three执行结束后出栈,再依次返回。
和提升的关系
函数声明会整体提升
js
foo()
function foo() {
console.log('ok')
}var 只提升声明,不提升赋值
js
console.log(count) // undefined
var count = 1let 和 const 也会进入上下文,但有暂时性死区
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理解顺序
学习时建议按这个顺序串起来:
- 作用域与作用域链。
- 执行上下文。
- 变量提升。
this绑定。- 闭包。
这样很多分散知识点会连成一套模型。
