JavaScript 中的 作用域(Scope)是指变量和函数在代码中可访问的范围。它决定了哪些变量和函数是可以访问的、哪些是不可访问的。理解作用域对于编写高效和没有错误的 JavaScript 代码至关重要。
JavaScript 中有两种主要的作用域类型:
1. 全局作用域 (Global Scope)
全局作用域是 JavaScript 中最外层的作用域。任何在全局作用域中声明的变量或函数,在整个程序中都可以访问。
let globalVar = "I am a global variable";
function testGlobalScope() {
console.log(globalVar); // 访问全局变量
}
testGlobalScope(); // 输出 "I am a global variable"
在浏览器中,全局作用域通常是 window
对象。在 Node.js 中,全局作用域是 global
对象。
2. 函数作用域 (Function Scope)
每个函数都有自己的作用域。在函数内部声明的变量,只能在该函数内访问。如果在函数外部访问它,将会报错。
function myFunction() {
let localVar = "I am a local variable";
console.log(localVar); // 输出 "I am a local variable"
}
myFunction();
console.log(localVar); // 报错: ReferenceError: localVar is not defined
3. 块级作用域 (Block Scope)
块级作用域是通过 let
和 const
关键字引入的,它是作用于由花括号 {}
包裹的代码块(例如,if 语句、循环等)中的。块级作用域的变量在块外不可访问。
if (true) {
let blockVar = "I am inside the block";
console.log(blockVar); // 输出 "I am inside the block"
}
console.log(blockVar); // 报错: ReferenceError: blockVar is not defined
在传统的 JavaScript 中,只有函数作用域(由 var
声明的变量)和全局作用域是有效的。let
和 const
是 ES6 引入的,它们提供了块级作用域。
4. 词法作用域 (Lexical Scope)
JavaScript 是 词法作用域的语言,意味着变量的作用域是在代码编写时确定的,而不是运行时。即,嵌套函数可以访问其外部函数的变量,这种访问是基于它们在代码中的位置来决定的。
function outer() {
let outerVar = "I am outside";
function inner() {
console.log(outerVar); // 访问 outer() 函数中的变量
}
inner();
}
outer(); // 输出 "I am outside"
5. 作用域链 (Scope Chain)
作用域链是 JavaScript 在查找变量时所遵循的规则。如果在当前作用域中找不到变量,JavaScript 会沿着作用域链向外层作用域查找,直到找到该变量或到达全局作用域。如果找不到,JavaScript 会抛出 ReferenceError
。
6. 闭包 (Closure)
闭包是指一个函数可以访问其外部作用域的变量,即使外部函数已经执行完毕。这种特性允许在函数执行后仍然保留对外部作用域变量的引用。
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer(); // 返回的 inner 函数是一个闭包
counter(); // 输出 1
counter(); // 输出 2
在这个例子中,inner
函数形成了一个闭包,它可以访问 outer
函数的 count
变量,即使 outer
函数已经返回。
7. 变量提升 (Hoisting)
JavaScript 中,使用 var
声明的变量会被提升到函数作用域的顶部,但不会初始化。也就是说,在声明之前访问变量会得到 undefined
。但是 let
和 const
声明的变量是不会提升的,访问它们会导致错误。
console.log(a); // 输出 undefined
var a = 10;
console.log(b); // 报错: ReferenceError: Cannot access 'b' before initialization
let b = 20;
小结
- 全局作用域:在整个程序中都可访问的作用域。
- 函数作用域:函数内部的作用域。
- 块级作用域:通过
let
和const
引入的作用域,限定在代码块内部。 - 词法作用域:作用域的查找顺序在编译时就已经决定。
- 闭包:函数能“记住”并访问它创建时的作用域。
- 作用域链:访问变量时,JavaScript 会沿着作用域链逐层查找。
掌握 JavaScript 作用域的规则有助于避免很多常见的错误,并帮助开发者编写更干净和可维护的代码。