icon
作用域与作用域链

作用域、作用域链

1. 作用域定义了变量、函数或对象的可访问范围。JavaScript中有以下作用域类型:

全局作用域
  • 在代码最外层声明的变量或函数属于全局作用域。
  • 全局变量可以在代码的任何地方访问。
const globalVar = "I'm global";

function showGlobal() {
  console.log(globalVar); // 可访问
}
showGlobal();

函数作用域
  • 在函数内部声明的变量或函数仅在该函数内可访问。
function myFunction() {
  const localVar = "I'm local";
  console.log(localVar); // 可访问
}
console.log(localVar); // 报错:localVar未定义

块级作用域
  • 由let和const声明的变量在{}(如if、for、while等)中形成块级作用域。
if (true) {
  const blockVar = "I'm block-scoped";
  console.log(blockVar); // 可访问
}
console.log(blockVar); // 报错:blockVar未定义

2. 作用域链是JavaScript查找变量的机制。当访问一个变量时,引擎会按以下顺序查找:

‌当前作用域‌:先在当前函数或块中查找变量。 ‌外层作用域‌:如果未找到,逐层向外层作用域(父函数或全局)查找。 ‌全局作用域‌:如果仍未找到,在全局作用域中查找;若不存在则报错。 ‌

2.1 作用域链的形成‌

每个函数在创建时会记录其所在的作用域链。 嵌套函数会继承外层函数的作用域链。

const globalVar = "Global";

function outer() {
  const outerVar = "Outer";

  function inner() {
    const innerVar = "Inner";
    console.log(innerVar);    // Inner(当前作用域)
    console.log(outerVar);    // Outer(外层作用域)
    console.log(globalVar);   // Global(全局作用域)
  }
  inner();
}
outer();

‌2.2 闭包与作用域链‌

闭包是函数与其作用域链的结合。即使外层函数执行完毕,内层函数仍能访问外层函数的变量:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  };
}
const counter = createCounter();
counter(); // 1
counter(); // 2 (count变量通过闭包保留)

‌作用域污染是什么?‌

作用域污染(Scope Pollution)是指在代码中无意或过度地将变量、函数或其他标识符暴露到全局作用域或更高层级的作用域中,导致命名冲突、意外的变量覆盖或难以维护的代码。

  1. ‌全局变量滥用‌:在全局作用域中定义大量变量,可能与其他脚本或库中的变量冲突。
var a = 1; // 全局变量
function foo() {
    b = 2; // 未声明变量,自动成为全局变量
}

  1. ‌变量提升的副作用‌:使用var声明变量时,变量会被提升到函数作用域顶部,可能引发意外行为。
console.log(x); // undefined(变量提升)
var x = 10;

  1. 闭包中的变量泄露‌:闭包中未妥善管理的变量可能长期占用内存或影响外部作用域。

‌如何避免作用域污染?

  • 使用模块化(如ES6的import/export)或IIFE(立即执行函数)隔离作用域。
  • 显式声明变量:避免省略var、let或const,防止变量意外成为全局变量。
  • 优先使用let和const代替var,限制变量仅在当前块中有效。
  • 使用ES6模块化或CommonJS规范,将代码分割为独立模块,避免全局命名冲突。
  • 将全局变量封装到单一对象中,减少全局标识符数量。
  • 启用严格模式("use strict"),禁止隐式全局变量和其他不安全操作。
  • 不要直接修改Array.prototype、Object.prototype等内置对象的原型,可能导致与其他库冲突。
  • 及时释放闭包中不再需要的引用,避免内存泄漏。