作用域链(Scope Chain)
在 JavaScript 中,作用域链(Scope Chain) 是当前执行上下文的作用域与所有外层嵌套作用域的有序集合,核心作用是解析变量 / 函数的访问权限—— 当代码需要访问一个变量时,JS 引擎会沿着作用域链从 “当前作用域” 到 “外层作用域” 依次查找,直到找到变量或到达全局作用域为止。
一、作用域链的核心基础
要理解作用域链,需先明确两个前提:
作用域的类型
:
- 全局作用域:代码最外层,全局有效,浏览器中挂载到
window; - 函数作用域:函数内部声明的变量仅函数内有效(
var声明); - 块级作用域:
{}包裹的区域(let/const声明);
- 全局作用域:代码最外层,全局有效,浏览器中挂载到
作用域的嵌套:JS 支持作用域嵌套(如函数内嵌套函数),内层作用域可访问外层作用域的变量,反之不行;
执行上下文:每个函数调用 / 全局代码执行时,会创建一个 “执行上下文”,包含当前作用域、作用域链、
this等信息,作用域链是执行上下文的核心属性。
二、作用域链的构成与查找规则
1. 构成(从内到外)
- 当前执行上下文的词法环境:即当前作用域(如函数内部、块级作用域),存储当前声明的变量 / 函数;
- 外层嵌套作用域的词法环境:一层一层向外追溯,直到全局作用域;
- 全局词法环境:作用域链的终点,存储全局变量 / 函数(如
window、console)。
2. 查找规则(核心:“就近原则 + 单向查找”)
- 查找变量时,先在当前作用域找,找到则直接使用;
- 若未找到,沿作用域链向上一层查找;
- 直到全局作用域,若仍未找到,非严格模式下会隐式创建全局变量(严格模式下报错
ReferenceError); - 作用域链查找是单向的:内层可访问外层,外层无法访问内层。
三、直观示例解析
示例 1:基础嵌套作用域链
1 | // 全局作用域(作用域链的最外层) |
inner 函数的作用域链(从内到外):inner 作用域 → outer 作用域 → 全局作用域
示例 2:块级作用域的作用域链(let/const)
1 | const global = "全局"; |
内层块的作用域链:内层块作用域 → 外层块作用域 → 全局作用域
示例 3:作用域链的 “就近覆盖”
1 | var a = 10; // 全局 a |
四、作用域链的本质:词法作用域(静态作用域)
JS 的作用域链基于词法作用域(静态作用域) —— 作用域链的结构在代码编写阶段(词法分析阶段)就确定,而非执行阶段。换句话说:作用域链由代码的 “嵌套结构” 决定,与函数调用位置无关。
示例:词法作用域的关键验证
1 | var x = "全局 x"; |
核心原因:inner 函数的作用域链在定义时就绑定了 outer 作用域,无论 inner 在哪里调用,作用域链都不会改变。
五、作用域链与变量提升的关系
变量提升是 “作用域内的声明提前”,而作用域链是 “多个作用域的查找顺序”,二者结合决定了变量的访问规则:
1 | console.log(a); // undefined(全局作用域提升,值为 undefined) |
解析:
- 全局执行上下文:作用域链是
全局作用域,a提升后值为undefined,赋值后为 1; - fn 执行上下文:作用域链是
fn 作用域→全局作用域,fn 内的a提升后值为undefined,优先覆盖全局a,因此 fn 内打印undefined。
六、作用域链的常见坑点
1. 循环中的 var 无块级作用域
1 | // 错误示例:var 无块级作用域,所有 setTimeout 共享同一个 i |
2. 内层作用域未声明变量,误修改全局变量
1 | var num = 10; |
规避:内层作用域使用变量前,务必用 let/const 声明,避免污染外层。
七、总结
作用域链的核心:执行上下文的作用域集合,用于变量查找,遵循 “从内到外、单向查找” 规则;
本质:基于词法作用域(静态作用域),由代码编写时的嵌套结构决定,与调用位置无关;
作用:保障变量的访问权限(内层可访问外层,外层不可访问内层),避免变量冲突;
最佳实践
:
- 用
let/const声明变量,利用块级作用域细化作用域链,减少全局污染; - 避免在内层作用域直接修改外层变量,如需修改可通过函数参数 / 返回值传递;
- 理解词法作用域,避免 “函数调用位置影响变量查找” 的误区。
- 用



