作用域本质上是 ​代码执行时确定变量访问权限的规则,与执行上下文(Execution Context)、词法环境(Lexical Environment)、作用域链(Scope Chain)直接相关。


核心概念

作用域类型
全局作用域(Global Scope)函数作用域(Function Scope)块级作用域(Block Scope,ES6 let/const
模块作用域(Module Scope,ES6 模块化)
词法作用域

作用域链 • 函数创建时保存外部作用域的引用(通过 [[Environment]] 属性)。
• 执行时通过作用域链逐级查找变量(从当前作用域 → 外层作用域 → 全局作用域)。
• 闭包(Closure)通过维持作用域链实现对外部变量的长期引用。

作用域与执行上下文的关系
• 执行上下文(Execution Context)包含词法环境(Lexical Environment),词法环境决定作用域。
• 生命周期:作用域在代码定义时静态确定,执行上下文在函数调用时动态创建。


关键机制

变量查找规则
• LHS(Left-Hand Side)查询:变量赋值时查找存储位置。
• RHS(Right-Hand Side)查询:变量取值时查找具体值。
• 严格模式下的限制:未声明的变量直接赋值会抛出 ReferenceError
• 暂时性死区(Temporal Dead Zone,TDZ):let/const 声明前访问变量会报错。

闭包与作用域泄漏
• 闭包维持作用域链,可能导致外部变量无法被垃圾回收(内存泄漏)。
• 解决方法:手动清除引用(如 element.removeEventListener)或使用弱引用(WeakMap)。

ES6 新特性
let/const 引入块级作用域,替代 var 的函数作用域。
• 模块作用域(import/export)隔离变量,避免全局污染。


常见误区

变量提升(Hoisting)
var 声明的变量会提升到作用域顶部,但值为 undefined
function 声明整体提升,let/const 不提升(存在 TDZ)。

全局作用域污染
• 未使用 var/let/const 直接赋值变量,会隐式创建全局变量。
• 示例:function foo() { a = 10 }a 成为全局变量。

闭包误用
• 循环中创建闭包可能导致意外引用同一变量(需通过 IIFE 或 let 解决)。


在 JavaScript 中,作用域(Scope)决定了变量和函数的可访问性。以下是不同类型作用域的梳理:


1. 全局作用域(Global Scope)

  • 定义:代码最外层定义的变量或函数,任何地方都能访问。
  • 特点
    • 浏览器中,全局作用域的变量通过 var 声明会成为 window 对象的属性,而 let/const 不会。
    • 模块化开发(ES6+)中,每个模块的顶级作用域是独立的,不会污染全局。
  • 示例
    var globalVar = "全局变量"; // 可通过 window.globalVar 访问
    let globalLet = "全局let变量"; // 不在 window 对象上

2. 函数作用域(Function Scope)

  • 定义:在函数内部声明的变量,仅函数内可访问。
  • 特点
    • var 声明的变量具有函数作用域,会提升到函数顶部。
    • 每次调用函数会创建新的作用域,闭包基于此特性实现。
  • 示例
    function foo() {
      var funcVar = "函数内部变量";
      console.log(funcVar); // 可访问
    }
    foo();
    console.log(funcVar); // 报错:funcVar未定义

3. 块级作用域(Block Scope,ES6 let/const

  • 定义:由 {} 包裹的代码块(如 iffor)内声明的变量,仅块内可访问。
  • 特点
    • let/const 声明的变量具有块级作用域,不会变量提升,存在暂时性死区(TDZ)。
    • var 在块内声明的变量会泄露到外层作用域。
  • 示例
    if (true) {
      let blockLet = "块内变量";
      var blockVar = "var变量";
    }
    console.log(blockVar); // "var变量"
    console.log(blockLet); // 报错:blockLet未定义

4. 模块作用域(Module Scope,ES6 模块化)

  • 定义:每个 ES6 模块文件(<script type="module">)拥有独立的作用域。
  • 特点
    • 模块顶层声明的变量默认私有,需通过 export 导出,import 导入后才能跨模块访问。
    • 避免全局污染,支持静态依赖分析。
  • 示例
    // module.js
    let moduleVar = "模块变量";
    export { moduleVar };
     
    // main.js
    import { moduleVar } from './module.js';
    console.log(moduleVar); // "模块变量"

5. 词法作用域(Lexical Scope,静态作用域)

  • 定义:作用域在代码编写时确定,而非运行时。函数在定义时捕获外层作用域的变量。
  • 词法作用域:实现原理
  • 特点
    • JavaScript 采用词法作用域,闭包基于此实现。
    • 动态作用域(如 this)是例外,但变量查找仍遵循词法作用域。
  • 示例
    let outerVar = "外部变量";
    function outer() {
      let innerVar = "内部变量";
      function inner() {
        console.log(outerVar); // "外部变量"(词法作用域)
        console.log(innerVar); // "内部变量"
      }
      return inner;
    }
    const innerFunc = outer();
    innerFunc();

总结对比表

作用域类型关键字/机制作用域边界变量提升典型场景
全局作用域var(非模块化)全局全局配置、工具函数
函数作用域varfunction函数体 {}局部变量、闭包
块级作用域let/const{}循环、条件语句
模块作用域export/import模块文件模块化开发
词法作用域代码结构(静态)定义时确定闭包、嵌套函数

关键点

  1. 作用域链:变量查找从当前作用域逐级向外,直至全局作用域。
  2. 闭包:函数保留其词法作用域的引用,即使在其外部作用域执行完毕。
  3. 严格模式use strict 下禁止隐式全局变量,提升安全性。