作用域本质上是 代码执行时确定变量访问权限的规则,与执行上下文(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
)
- 定义:由
{}
包裹的代码块(如if
、for
)内声明的变量,仅块内可访问。 - 特点:
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 (非模块化) | 全局 | 是 | 全局配置、工具函数 |
函数作用域 | var 、function | 函数体 {} | 是 | 局部变量、闭包 |
块级作用域 | let /const | 块 {} | 否 | 循环、条件语句 |
模块作用域 | export /import | 模块文件 | 否 | 模块化开发 |
词法作用域 | 代码结构(静态) | 定义时确定 | 无 | 闭包、嵌套函数 |
关键点
- 作用域链:变量查找从当前作用域逐级向外,直至全局作用域。
- 闭包:函数保留其词法作用域的引用,即使在其外部作用域执行完毕。
- 严格模式:
use strict
下禁止隐式全局变量,提升安全性。