理解JS中的作用域

tianyi Lv3

Js作用域在开发中无处不在,可以说没有js作用域就没有了可以正确被访问变量的可能。简单来说js作用域用于控制变量和函数的可访问范围,一旦超出这个范围,变量和函数便不能被访问,作用域分为几类全局作用域函数作用域块级作用域词法作用域

作用域看着简单,实际上还是有一些可以深挖的点的,比如作用域链闭包和作用域的联系如何避免全局变量的污染使用 IIFE(立即调用函数表达式)来创建一个私有作用域,如何在 JavaScript 中实现私有变量和方法,什么是“动态作用域”(JS中没有动态作用域,这里只是举一个例子),并讨论 JavaScript 是如何使用词法作用域的等等。

进入主题

全局作用域,函数作用域,块级作用域等基础概念只做简单的介绍,不做过多涉及

全局作用域

在 JavaScript 中,任何在函数外部声明的变量都是全局变量,属于全局作用域。全局变量可以在任何地方访问,包括函数内部。

1
2
3
4
5
6
7
var globalVar = "I am global";

function testGlobal() {
console.log(globalVar); // 可以访问全局变量
}

testGlobal(); // 输出 "I am global"

函数作用域

每当一个函数被创建时,都会创建一个新的作用域。函数内部声明的变量只能在该函数内部访问,外部无法访问。

1
2
3
4
5
6
7
function testFunctionScope() {
var localVar = "I am local";
console.log(localVar); // 可以访问
}

testFunctionScope(); // 输出 "I am local"
console.log(localVar); // 报错:localVar is not defined

块级作用域

ES6 引入了 let 和 const 关键字,它们提供了块级作用域。块级作用域是指在 {} 代码块内声明的变量只在该代码块内有效。

1
2
3
4
5
6
if (true) {
let blockVar = "I am in a block";
console.log(blockVar); // 可以访问
}

console.log(blockVar); // 报错:blockVar is not defined

作用域链

作用域链是一个由多个作用域组成的结构,形成了一个链式的访问路径。每当一个函数被调用时,JavaScript 会创建一个新的执行上下文,并将其作用域链与当前作用域链相结合。作用域链的结构确保了在函数内部可以访问到其外部的变量
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var globalVar = "I am a global variable";

function outerFunction() {
var outerVar = "I am an outer variable";

function innerFunction() {
var innerVar = "I am an inner variable";
console.log(innerVar); // 访问内层变量
console.log(outerVar); // 访问外层变量
console.log(globalVar); // 访问全局变量
}

innerFunction();
}

outerFunction();

闭包与作用域链

闭包是 JavaScript 中一个强大的特性,它允许函数访问其外部作用域的变量,即使外部函数已经返回。闭包的存在使得作用域链得以延续,确保了对外部变量的访问。

1
2
3
4
5
6
7
8
9
10
11
12
function makeCounter() {
let count = 0; // 私有变量

return function() {
count++; // 访问外部变量
return count;
};
}

const counter = makeCounter();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2

在这个例子中,makeCounter 返回了一个函数,这个函数形成了一个闭包,保持了对 count 变量的引用。即使 makeCounter 执行完毕,count 依然可以被访问。

作用域链的存在对 JavaScript 的性能和内存管理有重要影响:

  • 内存管理:闭包会保持对外部变量的引用,这可能导致内存泄漏。如果不恰当地使用闭包,可能会导致不再需要的变量无法被垃圾回收
  • 变量查找:作用域链的查找过程会影响变量访问的速度。局部变量的查找速度通常比全局变量快,因为局部变量在当前作用域中直接可用,而全局变量则需要逐层查找。

使用 IIFE(立即调用函数表达式)来创建一个私有作用域

立即调用函数表达式(IIFE, Immediately Invoked Function Expression)是一种常用的 JavaScript 编程模式,主要用于创建一个私有作用域,从而避免变量污染全局作用域。IIFE 允许你在函数内部定义变量和函数,这些变量和函数不会暴露到外部作用域,能够有效地封装代码。

什么是IIFE

IIFE 是一个在定义后立即执行的函数。它的基本语法如下:

1
2
3
4
5
6
(function() {
// 代码块
})();
(() => {
// 代码块
})();

作用

通过 IIFE,你可以创建一个私有作用域,确保在 IIFE 内部定义的变量不会被外部访问。这对于封装逻辑避免全局命名冲突以及保护变量不被外部修改非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var globalVar = "I am a global variable";

(function() {
var privateVar = "I am a private variable"; // 私有变量

console.log(privateVar); // 可以访问
console.log(globalVar); // 可以访问全局变量

// 定义一个私有函数
function privateFunction() {
console.log("This is a private function.");
}

privateFunction(); // 调用私有函数
})();
// 尝试访问私有变量和函数
console.log(privateVar); // ReferenceError: privateVar is not defined
privateFunction(); // ReferenceError: privateFunction is not defined

应用场景

封装模块:使用 IIFE 可以创建模块化的代码结构,避免全局命名冲突。例如,可以将相关的功能封装在一个 IIFE 中,形成一个模块。

1
2
3
4
5
6
7
8
9
10
11
12
var myModule = (function() {
var privateVar = "I am private";

return {
getPrivateVar: function() {
return privateVar;
}
};
})();

console.log(myModule.getPrivateVar()); // 输出 "I am private"
console.log(privateVar); // ReferenceError: privateVar is not defined

动态作用域

动态作用域(Dynamic Scope)是一种变量作用域的管理方式,与静态作用域(或词法作用域)相对。在动态作用域中,变量的作用域是根据程序的调用栈(call stack)来决定的,而不是根据代码的结构来决定的。这意味着一个函数可以访问在其调用链中定义的变量,而不仅仅是它自己定义的变量。

注意:在 JavaScript 中,没有动态作用域(dynamic scope),而是采用了 词法作用域(lexical scope)。

动态作用域的特点

  1. 调用上下文:
    • 在动态作用域中,变量的可见性取决于函数的调用顺序。当一个函数被调用时,它可以访问所有在其调用链中定义的变量。
  2. 运行时决定:
    • 变量的解析是在运行时进行的,而不是在编写代码时。这使得动态作用域的行为在某些情况下可能会更加灵活,但也可能导致不易预测的结果。
  3. 对嵌套函数的影响:
    • 如果一个函数在另一个函数内部被调用,内层函数可以访问外层函数中的变量,但这取决于调用的上下文。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function outer() {
      var x = 10;

      function inner() {
      print(x); // 这里的 x 是从 outer 函数中获取的
      }

      inner(); // 调用 inner 函数
      }

      outer(); // 输出 10
  • Title: 理解JS中的作用域
  • Author: tianyi
  • Created at : 2025-05-05 09:53:02
  • Updated at : 2025-05-05 10:07:06
  • Link: https://github.com/ztygod/2025/05/05/理解JS中的作用域/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments