💡 本篇目标:彻底理解 JavaScript 中作用域的本质、形成机制、作用域链的运行逻辑,并通过可视化图解 + 代码演示 + 实战示例,建立你对 JS 底层执行模型的“源代码级”理解。
🎯 一、你以为的作用域,其实只是“静态规则”
❓什么是作用域(Scope)?
作用域就是变量访问的范围,决定了在什么位置能访问到某个变量。
JS 是词法作用域(Lexical Scope)语言,也就是说:
- 作用域在你写代码时就已经决定了
- 跟你怎么调用这个函数无关
🧠 对比图:词法作用域 vs 动态作用域
| 类型 | 特点 | 举例 |
|---|---|---|
| 词法作用域 | 静态、由代码结构决定 | JavaScript、C、Go |
| 动态作用域 | 动态、由调用栈决定 | bash、某些 Lisp |
📦 二、执行上下文(Execution Context)vs 词法环境(Lexical Environment)
✅ 术语区分:
| 概念 | 说明 |
|---|---|
| 执行上下文 | 当前 JS 执行时的状态(变量、作用域链、this 等) |
| 词法环境 | 是执行上下文的一部分,维护变量-值映射的结构 |
| 作用域链 | 多个词法环境通过 outer 连接形成的“查找路径” |
🧩 执行过程简图:
代码结构决定:
function foo() {
var a = 10
function bar() {
console.log(a)
}
bar()
}
执行栈过程如下:
- 进入全局执行上下文(创建 globalEnv)
- 创建
foo函数 → 记录其词法环境指向 globalEnv - 调用
foo()→ 创建 fooEC,fooEnv → outer → globalEnv - 调用
bar()→ 创建 barEC,barEnv → outer → fooEnv
🔬 三、作用域链的运行逻辑(图解)
const a = 1
function outer() {
const b = 2
function inner() {
const c = 3
console.log(a, b, c)
}
inner()
}
outer()
内部结构可视化(作用域链):
innerEnv → outerEnv (outer) → outerEnv (global)
c b a
变量查找机制是:从当前作用域向外一层一层找,直到 global,找不到就报错
✅ 四、工程实战:作用域链是如何用于权限控制的?
示例:构建一个私有变量模块(闭包 + 作用域链)
function createCounter() {
let count = 0 // 作用域链上的私有变量
return {
increment() {
count++
return count
},
getCount() {
return count
}
}
}
const counter = createCounter()
console.log(counter.increment()) // 1
console.log(counter.getCount()) // 1
📌 本质解析:
createCounter执行后,其count被保存在[[Environment]]中increment、getCount拿到的是 闭包 + 作用域链形成的访问权- 外部无法直接访问
count,实现了封装与数据安全
🧨 五、常见误区与注意点
1. 作用域不是调用时才决定的
var a = 1
function foo() {
console.log(a)
}
function bar() {
var a = 2
foo() // 输出 1,而不是 2
}
bar()
因为 foo 在定义时,其作用域链已经锁定在 global,不会因为在 bar 中调用而切换作用域。
2. 不理解作用域链会导致“闭包陷阱”
const fns = []
for (var i = 0; i < 3; i++) {
fns.push(function () {
console.log(i) // 全部输出 3
})
}
fns[0]() // 3 ❌
✅ 修正方式一:使用 IIFE
for (var i = 0; i < 3; i++) {
(function(i) {
fns.push(function () {
console.log(i)
})
})(i)
}
✅ 修正方式二:使用 let(块级作用域)
for (let i = 0; i < 3; i++) {
fns.push(() => console.log(i)) // 输出 0,1,2
}
🚀 六、总结:你现在应该掌握了
| 知识点 | 理解程度 |
|---|---|
| JS 是词法作用域语言 | ✅ |
| 执行上下文 vs 词法环境 | ✅ |
| 作用域链查找顺序 | ✅ |
| 如何利用作用域封装私有数据 | ✅ |
| 容易出错的场景与修复方法 | ✅ |
📘 下一篇《第2篇:变量提升与函数提升的编译原理剖析》将深入源码层级,揭示 JS 是如何在执行前构建作用域和变量声明的。