作用域是程序中定义变量的区域,它规定了变量和函数的可访问范围,以及变量的生命周期。
1. 作用域的核心概念
1.1 作用域的基本作用
- 确定变量和函数的可见性:在哪些地方可以访问这些标识符
- 确定变量的生命周期:变量何时创建、何时销毁
- 避免命名冲突:不同作用域可以有同名变量
- 提供访问控制:实现私有和公共的访问级别
2. JavaScript中的作用域类型
2.1 全局作用域(Global Scope)
// 全局作用域中的变量
var globalVar = "I'm global";
let globalLet = "I'm also global";
const globalConst = "Me too";
function globalFunction() {
console.log("I'm a global function");
}
// 这些都可以在代码的任何地方访问
console.log(window.globalVar); // 浏览器中,var变量会成为window对象的属性
2.2 函数作用域(Function Scope)
function outer() {
// 函数作用域开始
var functionScoped = "I'm only available inside outer";
let alsoFunctionScoped = "Me too";
function inner() {
// 可以访问外层函数的变量
console.log(functionScoped); // 正常访问
var innerVar = "I'm only in inner";
}
inner();
// console.log(innerVar); // 报错:innerVar is not defined
}
// console.log(functionScoped); // 报错:functionScoped is not defined
2.3 块级作用域(Block Scope)
// ES6引入的let和const具有块级作用域
{
// 块级作用域开始
let blockScoped = "I'm only in this block";
const blockConst = "Me too";
console.log(blockScoped); // 正常
}
// console.log(blockScoped); // 报错:blockScoped is not defined
// 实际应用场景
for (let i = 0; i < 3; i++) {
// 每个i都是独立的块级作用域
setTimeout(() => console.log(i)); // 0, 1, 2
}
// 对比var(没有块级作用域)
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j)); // 3, 3, 3
}
2.4 模块作用域(Module Scope)
// module.js
const privateVar = "I'm private to this module";
export const publicVar = "I'm exported";
// 在模块外无法访问privateVar
// 只能访问通过export暴露的变量
3. 作用域链(Scope Chain)
3.1 什么是作用域链
当访问一个变量时,JavaScript引擎会从当前作用域开始查找,如果找不到,就向上一层作用域查找,直到全局作用域,这个查找路径就是作用域链。
let global = "global";
function outer() {
let outerVar = "outer";
function inner() {
let innerVar = "inner";
console.log(innerVar); // 1. 在当前作用域找到
console.log(outerVar); // 2. 在外层作用域找到
console.log(global); // 3. 在全局作用域找到
console.log(undeclared); // 4. 报错:在所有作用域都找不到
}
inner();
}
outer();
3.2 作用域链的创建
// 作用域链在函数定义时确定(词法作用域)
let globalVar = "global";
function createFunctions() {
let outerVar = "outer";
function inner1() {
console.log(outerVar); // 可以访问outerVar
}
let inner2 = function() {
console.log(outerVar); // 也可以访问outerVar
};
// 即使函数在其他地方执行,仍然能访问定义时的作用域链
return [inner1, inner2];
}
const [func1, func2] = createFunctions();
func1(); // "outer" - 仍然能访问outerVar(闭包)
func2(); // "outer"
4. 词法作用域 vs 动态作用域
4.1 JavaScript使用词法作用域(静态作用域)
let x = 10;
function foo() {
console.log(x);
}
function bar() {
let x = 20;
foo(); // 输出10,不是20
}
bar();
// 解释:foo在定义时确定作用域链,访问的是定义时的x,而不是调用时的x
4.2 对比动态作用域(JavaScript不是动态作用域)
// 伪代码,展示动态作用域的概念
// 如果JavaScript是动态作用域:
x = 10
function foo() {
print(x) // 在动态作用域中,这里会查找调用时的作用域
}
function bar() {
x = 20
foo() // 会输出20
}
5. 特殊的作用域行为
5.1 变量提升(Hoisting)
// var的变量提升
console.log(a); // undefined,不会报错
var a = 5;
// 实际执行顺序:
var a; // 声明提升到作用域顶部
console.log(a); // undefined
a = 5; // 赋值留在原地
// let/const的暂时性死区(Temporal Dead Zone)
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 5;
5.2 没有块级作用域的var
if (true) {
var noBlockScope = "I escape the block";
let hasBlockScope = "I stay in the block";
}
console.log(noBlockScope); // "I escape the block"
console.log(hasBlockScope); // 报错:hasBlockScope is not defined
6. 作用域的实际应用
6.1 避免全局污染
// 不好的做法:污染全局作用域
var global1 = "data";
var global2 = "more data";
// 好的做法:使用IIFE或模块
(function() {
// 私有作用域
var privateData = "hidden";
// 对外暴露必要的接口
window.myModule = {
publicMethod: function() {
return privateData;
}
};
})();
6.2 创建私有变量
function createCounter() {
let count = 0; // 私有变量
return {
increment() {
count++;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.count); // undefined,无法直接访问
console.log(counter.getCount()); // 0
6.3 循环中的块级作用域
// 常见问题
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() {
console.log(i); // 全部输出3
});
}
// 解决方案1:使用IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
funcs.push(function() {
console.log(j); // 0, 1, 2
});
})(i);
}
// 解决方案2:使用let(推荐)
for (let i = 0; i < 3; i++) {
funcs.push(function() {
console.log(i); // 0, 1, 2
});
}
7. 现代JavaScript中的作用域
7.1 ES6模块作用域
// module.js
export const publicValue = 42;
const privateValue = "secret";
// main.js
import { publicValue } from './module.js';
console.log(publicValue); // 42
console.log(privateValue); // 报错:privateValue is not defined
7.2 类作用域
class MyClass {
// 类作用域
#privateField = "I'm private";
publicField = "I'm public";
constructor() {
this.instanceField = "I'm on the instance";
}
method() {
console.log(this.#privateField); // 可以访问私有字段
}
}
const instance = new MyClass();
console.log(instance.publicField); // "I'm public"
console.log(instance.#privateField); // 报错:Private field must be declared in an enclosing class
总结表格
| 作用域类型 | 关键字 | 特点 | 示例 |
|---|---|---|---|
| 全局作用域 | var, let, const | 整个程序可访问 | var global = 1; |
| 函数作用域 | var | 只在函数内可访问 | function foo() { var x = 1; } |
| 块级作用域 | let, const | 在{}内可访问 | { let x = 1; } |
| 模块作用域 | export, import | 只在模块内可访问 | export const x = 1; |
关键要点
- 作用域决定了变量的可见性和生命周期
- JavaScript使用词法作用域:作用域在代码编写时确定
- 作用域链决定了变量的查找顺序:从内到外查找
- ES6引入的let/const有块级作用域,解决了许多var的问题
- 合理使用作用域可以避免命名冲突、实现封装
理解作用域是掌握JavaScript闭包、this绑定、模块化等高级概念的基础。