在 JavaScript 开发中,作用域(Scope)和作用域链(Scope Chain) 是理解变量生命周期、函数执行、闭包机制的核心基础。它们决定了"在哪里可以访问到哪些变量"。
本文将系统性地讲解 全局作用域、函数作用域、块级作用域 ,并深入剖析 作用域链的形成与查找机制,帮你构建清晰的执行环境认知。
一、什么是作用域?------ 变量的"可见范围"
作用域 是指代码中变量和函数的可访问性范围。它决定了一个变量"在哪些地方可以被使用"。
JavaScript 中主要有三种作用域:
类型 | 出现场景 | 特点 |
---|---|---|
全局作用域 | 最外层 | 所有代码都能访问 |
函数作用域 | function 内部 |
仅函数内部可访问 |
块级作用域 | {} 内部(let /const ) |
仅代码块内可访问 |
二、全局作用域:处处可见的"公共区域"
✅ 哪些变量属于全局作用域?
-
最外层定义的变量和函数
jsvar globalVar = 'I am global'; function globalFunc() { }
-
未声明直接赋值的变量(不推荐!)
jsfunction badFunc() { badVar = 'I am accidentally global'; } badFunc(); console.log(badVar); // 'I am accidentally global'
-
window
对象的属性jswindow.appName = 'MyApp'; console.log(appName); // 'MyApp' ------ 全局变量
⚠️ 全局作用域的弊端
- 命名冲突:多个脚本可能定义同名变量,导致覆盖;
- 内存泄漏:全局变量不会被垃圾回收;
- 安全性差:任何代码都可以修改全局状态。
📌 最佳实践:尽量减少全局变量,使用模块化或 IIFE 封装。
三、函数作用域:函数内部的"私有空间"
✅ 函数作用域的特点
- 在
function
内部声明的变量,只能在该函数内部访问; - 每次函数调用都会创建一个新的作用域;
- 内层作用域可以访问外层作用域的变量,反之不行。
js
function outer() {
var outerVar = 'outer';
function inner() {
var innerVar = 'inner';
console.log(outerVar); // ✅ 可以访问外层变量
console.log(innerVar); // ✅ 自身变量
}
inner();
console.log(innerVar); // ❌ 报错:innerVar is not defined
}
outer();
📌 关键点 :inner
可以访问 outerVar
,但 outer
无法访问 innerVar
。
四、块级作用域:ES6 的"精细控制"
✅ 什么是块级作用域?
由 {}
包裹的代码块(如 if
、for
、while
)中,使用 let
或 const
声明的变量,只能在该代码块内访问。
js
{
let blockVar = 'block';
const blockConst = 'const';
console.log(blockVar); // ✅
}
console.log(blockVar); // ❌ 报错
console.log(blockConst); // ❌ 报错
✅ 块级作用域的优势
特性 | var |
let /const |
---|---|---|
变量提升 | ✅ 有(值为 undefined ) |
❌ 无(存在"暂时性死区") |
重复声明 | ✅ 允许(会覆盖) | ❌ 不允许 |
作用域 | 函数作用域 | 块级作用域 |
循环中使用 | ❌ 容易出错 | ✅ 推荐 |
🔧 经典案例:循环中的 let
js
// 使用 var ------ 问题代码
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3 3 3
}
// 使用 let ------ 正确
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 0 1 2 ✅
}
let
在每次循环中创建一个新的绑定,避免了变量共享问题。
五、作用域链:变量查找的"寻宝地图"
✅ 什么是作用域链?
当 JavaScript 引擎查找一个变量时,它会按照以下顺序搜索:
- 当前作用域(当前执行上下文);
- 外层函数作用域;
- 再外层函数作用域;
- ......
- 全局作用域;
- 如果仍未找到,抛出
ReferenceError
。
这个逐层向上查找的路径 ,就是 作用域链。
🔍 作用域链示例
js
var global = 'global';
function func1() {
var a = 'a';
function func2() {
var b = 'b';
function func3() {
var c = 'c';
console.log(global, a, b, c); // 都能找到
}
func3();
}
func2();
}
func1();
作用域链结构:
markdown
func3 的作用域链:
1. func3 的变量对象(含 c)
2. func2 的变量对象(含 b)
3. func1 的变量对象(含 a)
4. 全局变量对象(含 global, func1)
✅ 作用域链的本质
- 是一个指向变量对象的指针列表;
- 前端始终是当前执行上下文的变量对象;
- 末端始终是全局对象(window/global);
- 保证了变量的有序访问。
✅ 自由变量(Free Variable)
在当前作用域中未定义,但被使用的变量,称为自由变量。
js
function outer() {
var x = 1;
function inner() {
console.log(x); // x 是自由变量
}
return inner;
}
inner
中的 x
不是 inner
自身定义的,所以是自由变量,需要通过作用域链向上查找。
六、图解:执行上下文与作用域链
css
执行 func3() 时:
[ 当前执行上下文: func3 ]
↓ 变量对象: { c: 'c' }
↓ 作用域链:
→ func3 VO
→ func2 VO { b: 'b' }
→ func1 VO { a: 'a' }
→ 全局 VO { global: 'global', func1: function }
查找 x
的过程:
- 在
func3 VO
中找 → 无; - 在
func2 VO
中找 → 无; - 在
func1 VO
中找 → 无; - 在全局 VO 中找 → 找到!
七、总结:核心要点一览
概念 | 关键点 |
---|---|
全局作用域 | 最外层,易污染,避免滥用 |
函数作用域 | function 内部,var 声明 |
块级作用域 | {} 内部,let /const 声明,无变量提升 |
作用域链 | 从内到外的变量查找路径 |
自由变量 | 非自身定义,需通过作用域链查找的变量 |
最佳实践 | 用 let /const 替代 var ,减少全局变量 |
💡 结语
"作用域是变量的家,作用域链是回家的路。"
理解作用域和作用域链,是掌握闭包、this
、模块化等高级特性的前提。无论你是初学者还是资深开发者,都应该定期回顾这些基础概念。
📌 记住:
- 内层可访问外层,外层不可访问内层;
let
/const
提供了更安全的块级作用域;- 作用域链是变量查找的唯一路径。