- 作用域是变量生存的”范围” 或 “区域”
- 全局作用域 (Global Scope):变量在整个程序中可用
- 局部或函数作用域 (Local or Function Scope):变量仅在函数内部可用
- 块级作用域 (Block Scope):变量仅在块内部可用
- 作用域链:JS 中用来解析变量名称并查找其实际值的机制
作用域概述
作用域是编程语言中的一个基本概念,它定义了变量的可访问性。这是为了管理变量的可见性和生命周期,以及避免命名冲突。
简单来说,作用域就是变量生存的 “范围” 或 “区域”。
在 JS 中,主要存在以下几种作用域:
- 全局作用域 (Global Scope)
- 局部或函数作用域 (Local or Function Scope)
- 块级作用域 (Block Scope)
除了明确的作用域规则,以下是与作用域相关的更高级的 JS 概念:
- 作用域链 (Scope Chain)
- 闭包 (Closures)
全局作用域 (Global Scope)
定义在所有代码结构之外的变量拥有全局作用域。在整个程序中,无论位置如何,都可以访问这些变量。
var globalVar = "I am a global variable";
function showGlobalVar() {
console.log(globalVar); // "I am a global variable"
}
showGlobalVar();
console.log(globalVar); // "I am a global variable"
在此示例中,globalVar
是在全局作用域中定义的,所以它可以在整个脚本中任何地方被访问。
函数作用域 (Function Scope)
在函数内部定义的变量拥有函数作用域,只能在该函数内部及其嵌套的函数中访问。这是 JS 早期版本中唯一的局部作用域形式。理解函数作用域对于防止变量泄漏到其他作用域中非常关键。
function showFunctionScope() {
var functionVar = "I am a function scoped variable";
console.log(functionVar); // "I am a function scoped variable"
}
showFunctionScope();
// console.log(functionVar); // 这会引发错误,因为 functionVar 在这里是不可访问的
在这里,functionVar
只在 showFunctionScope
函数内部是可访问的。如果你尝试在函数外部访问它,将会引发错误。
- 如果不使用 var 声明,和全局变量一致,表示给全局对象添加属性。(不建议这么干)
- 如果使用 var 声明,变量提升到所在函数的顶部,函数外部不可以使用该变量。
function test() {
a = 1;
var b = 2;
}
test(); // 执行一遍 test函数
window.a; // => 1
window.b; // => undefined
块级作用域 (Block Scope)
JS 支持基于代码块(如 if
语句或 for
循环内部)的作用域。
在这些语言中,使用特定关键字(如 JS 中的 let
和 const
)定义的变量在其所在的代码块及其子块中是可访问的。由于 let
和 const
的引入,块级作用域在现代 JS 开发中变得非常重要,因为它允许更精细地控制变量的生命周期。
if (true) {
let blockVar = "I am a block scoped variable";
console.log(blockVar); // "I am a block scoped variable"
}
// console.log(blockVar); // 这会引发错误,因为 blockVar 在这里是不可访问的
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
// console.log(i); // 这也会引发错误,因为 i 在这里是不可访问的
在此示例中,使用 let
关键字定义的 blockVar
变量和 i
变量只在它们所在的块内部是可访问的。尝试在块外部访问它们会引发错误。
作用域链 (Scope Chain)
作用域链 (Scope Chain) 是 JS 中用来解析变量名称并查找其实际值的机制。当代码在一个环境中执行时,它会创建一个作用域链来保证对执行环境内部及其外部的所有变量和函数的有序访问。
当我们在函数内部访问一个变量时,JS 会首先在当前函数的作用域中查找这个变量。如果没有找到,它会继续查找其外部函数的作用域,以此类推,直到找到这个变量或者达到全局作用域。
const a = "1";
function outer() {
const b = "2";
function inner() {
const c = "3";
const b = "4";
if (true) {
const a = "5";
console.log(a); // 5
}
console.log(b); // 4
console.log(c); // 3
}
inner();
}
outer();
在 inner
中,我们可以访问全局作用域、outer
的作用域以及它自己的作用域中的变量。并且查找的顺序是优先看里层再看外层,这就是作用域链的效果。
闭包 (Closures)
闭包是一个函数在其词法作用域外部引用了至少一个变量的现象。通过闭包,函数可以“记住”并访问其创建时所在的作用域。
当一个函数可以访问并操作其词法作用域外的变量时,就产生了闭包。
function createCounter() {
let count = 0;
return function() {
return count++;
};
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
在上面的例子中,当我们调用 createCounter()
,它返回一个匿名函数。这个匿名函数“记住”了 createCounter
函数作用域中的 count
变量,即使 createCounter()
已经执行完成。因此,每次我们调用 counter()
,它都会返回递增的 count
值。这就是闭包的一个经典示例。
demos
全局作用域
const a = 1
function f() {
console.log(a)
}
console.log(a) // 1
f() // 1
函数作用域
function f() {
const a = 1
console.log(a)
}
f() // 1
console.log(a) // ReferenceError: a is not defined
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>23.08.06</title>
</head>
<body>
<script>
function f() {
a = 1
console.log(a)
}
f() // 1
console.log(a) // 1
console.log(window.a) // 1
</script>
</body>
</html>
块级作用域
if (true) {
const a = 1
console.log(a) // 1
}
console.log(a) // ReferenceError: a is not defined
作用域链
const a = "1";
function outer() {
const b = "2";
function inner() {
const c = "3";
const b = "4";
if (true) {
const a = "5";
console.log(a); // 5
}
console.log(b); // 4
console.log(c); // 3
}
inner();
}
outer();
闭包
function createCounter() {
let count = 0;
return function() {
return count++;
};
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2