JavaScript 中的作用域(Scope)和作用域链(Scope Chain)。这是理解 JavaScript 如何工作的核心概念之一。
什么是作用域 (Scope)?
作用域就是变量与函数的可访问范围,即它控制着变量和函数的可见性与生命周期。换句话说,作用域决定了代码中哪些部分可以访问某个变量或函数。
JavaScript 主要有三种作用域:
- 全局作用域
- 函数作用域
- 块级作用域
-
全局作用域
在任何函数、代码块 {} 之外定义的变量或函数,都拥有全局作用域。
javascript// 全局变量 var globalVar = "我是全局变量"; let globalLet = "我是用let声明的全局变量"; const globalConst = "我是用const声明的全局变量"; // 全局函数 function globalFunction() { console.log("我是一个全局函数"); } // 在代码的任何地方都可以访问它们 console.log(globalVar); // "我是全局变量" globalFunction(); // "我是一个全局函数" function someFunction() { console.log(globalLet); // "我是用let声明的全局变量" (在函数内也可访问) console.log(globalConst); // "我是用const声明的全局变量" (在函数内也可访问) } someFunction()
注意:在浏览器环境中,全局作用域是 window 对象。使用 var 声明的全局变量会成为 window 对象的属性,但使用 let 和 const 声明的则不会。
javascriptvar myVar = 10; console.log(window.myVar); // 10 let myLet = 20; console.log(window.myLet); // undefined
-
函数作用域
在函数内部声明的变量拥有函数作用域。这意味着它们只能在定义它们的函数内部被访问。
javascriptfunction myFunction() { // 函数作用域变量 var functionScopedVar = "我在函数内部"; let functionScopedLet = "我也是!"; console.log(functionScopedVar); // 我在函数内部 console.log(functionScopedLet); // 我也是! } myFunction(); console.log(functionScopedVar); // ReferenceError: functionScopedVar is not defined console.log(functionScopedLet); // // ReferenceError: functionScopedLet is not defined
关键点:var 声明的变量是函数作用域的。无论它们在函数内部的哪个位置声明(不包括子函数),在整个函数内部都是可用的(由于变量提升)。
-
块级作用域
由 {} 创建的代码块(如 if 语句、for 循环、while 循环或单独使用 {})会形成一个块级作用域。只有使用 let 和 const 声明的变量才具有块级作用域。
javascriptif (true) { // 这是一个块 var blockScopedVar = "我是用 var 声明的"; // 没有块级作用域 let blockScopedLet = "我是用 let 声明的"; // 有块级作用域 const blockScopedConst = "我是用 const 声明的"; // 有块级作用域 } console.log(blockScopedVar); // "我是用 var 声明的" (因为 var 会穿透块) console.log(blockScopedLet); // ReferenceError: blockScopedLet is not defined console.log(blockScopedConst); // ReferenceError: blockScopedConst is not defined
for
循环的经典例子:javascript// 使用 var (没有块级作用域) for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 输出 3, 3, 3 } // 变量 i 是全局(或函数)作用域的,循环结束后值为 3,所有回调都引用同一个 i // 使用 let (有块级作用域) for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j), 100); // 输出 0, 1, 2 } // 每次迭代都会创建一个新的块级作用域变量 j,回调函数捕获的是当次迭代的 j
什么是作用域链 (Scope Chain)
当访问一个变量时,JavaScript 引擎会从当前作用域开始查找该变量。如果找不到,它会向外层 的作用域继续查找,一直找到全局作用域为止。这种逐级向上查找的链条关系,就叫做作用域链 。
作用域链是在函数定义时就被确定下来的,而不是在函数调用时。
javascript
let globalVariable = "I'm global";
function outerFunction() {
let outerVariable = "I'm outer";
function innerFunction() {
let innerVariable = "I'm inner";
console.log(innerVariable); // 在当前作用域找到 "I'm inner"
console.log(outerVariable); // 在当前作用域没找到,向上到 outerFunction 的作用域找到 "I'm outer"
console.log(globalVariable); // 在当前和 outer 都没找到,继续向上到全局作用域找到 "I'm global"
}
innerFunction();
}
outerFunction();
查找过程图示 :
当在 innerFunction 中访问变量时,查找顺序如下:
innerFunction 作用域
-> outerFunction 作用域
-> 全局作用域
这个链条关系在 innerFunction
被定义的时候就已经固定下来了,无论它在哪里被调用。
词法作用域
JavaScript 采用的作用域模型是词法作用域 (也称为静态作用域)。这意味着作用域是由代码中函数和块被书写的位置决定的,而不是由函数被调用的位置决定的。
javascript
let a = 10;
function foo() {
console.log(a);
}
function bar() {
let a = 20;
foo(); // 问:这里会输出什么?
}
bar(); // 输出 10,而不是 20!
解释:
函数 foo
在定义时,它的作用域链就已经确定了:foo 作用域
-> 全局作用域
。
当在 bar
内部调用 foo
时,foo
会沿着它自己定义时的作用域链去查找变量 a
,它在全局作用域中找到了 a = 10
。bar
内部定义的 a = 20
属于 bar
的作用域,不在 foo
的作用域链上,所以 foo
无法访问它。
总结
概念 | 描述 |
---|---|
作用域 | 变量和函数的可访问性范围。分为全局、函数和块级作用域。 |
全局作用域 | 在最外层定义,任何地方都可访问。 |
函数作用域 | 在函数内部定义,只能在函数内部访问。var 是函数作用域。 |
块级作用域 | 由 {} 创建,let 和 const 声明的变量仅限于块内访问。 |
作用域链 | 查找变量时,从当前作用域向外层作用域逐级查找的链条关系。 |
词法作用域 | 作用域在代码书写阶段(定义时)就被确定,而不是在执行时。这是理解闭包的关键。 |