词法作用域在函数定义时就确定了,变量可访问性取决于代码书写位置而非调用位置;作用域链在函数创建时固化于[[Environment]]中,沿词法嵌套逐级向上查找,与调用栈无关。

词法作用域在函数定义时就确定了,不是调用时
JavaScript 的词法作用域(Lexical Scope)意味着变量的可访问性取决于函数在代码中**书写的位置**,而不是它在哪里被调用。这和动态作用域完全不同——你不需要运行代码就能大致判断某个 var、let 或 const 是否能被访问。
比如嵌套函数中引用外层变量,只要外层变量在语法上“包围”了内层函数,就构成词法闭包。即使外层函数已经执行完毕,只要内层函数还存在(比如被返回或赋值给变量),它依然能访问那些变量。
-
function outer() { const x = 10; return function inner() { console.log(x); }; }——inner能访问x,因为定义时就在outer函数体内 -
const fn = outer(); fn();—— 此时outer已执行结束,但fn仍持有对x的引用 - 如果把
inner拿到全局去调用,它依然找的是定义时的外层作用域,不是调用时的全局作用域
作用域链是静态构建的查找路径,从当前作用域逐级向上
当 JS 引擎查找一个变量(比如 console.log(a) 中的 a),它不会跳着找,而是沿着一条预先确定的链路:先查当前执行上下文的词法环境(Local),没找到就查外层词法环境(Outer),再没找到就继续往上,直到全局对象(浏览器是 window,Node 是 globalThis)。这条链就是作用域链。
这个链在函数创建时就固化在函数的 [[Environment]] 内部槽里,跟调用栈无关。哪怕函数被传到别的模块或作为回调执行,它的作用域链也不会变。
立即学习“Java免费学习笔记(深入)”;
- 每个函数都有自己的
[[Environment]],指向其定义时的词法环境 - 全局代码的作用域链只有一层:全局环境
-
eval()有特殊规则,会临时插入当前作用域,但日常应避免使用 - 箭头函数没有自己的
this和arguments,但它同样遵循词法作用域——它的[[Environment]]指向定义时的外层函数环境
let/const 块级作用域会影响作用域链的“起点”
let 和 const 声明的变量绑定在块级词法环境中(比如 if、for、{}),而 var 只绑定在函数级或全局级。这意味着作用域链的“最底层”可能是一个块环境,而不只是函数环境。
不过要注意:块级环境本身不创建新的作用域链,它只是函数作用域内部的一个“子环境”。JS 引擎在查找变量时,会先检查当前块,再退回到所在函数,再往上。
function foo() {
if (true) {
let x = 1;
var y = 2;
}
console.log(y); // ✅ 可以访问,y 提升到 foo 作用域
console.log(x); // ❌ ReferenceError: x is not defined
}
-
var y被提升并绑定到foo的函数环境,所以能在if外访问 -
let x绑定在if块的环境记录中,foo的作用域链里不包含该块环境(除非当前执行流就在该块内) - 这种差异导致你在调试时看到的“作用域面板”中,块级变量只在对应块内可见
常见误判点:this 不参与作用域链查找
很多人混淆 this 和词法作用域,以为 this 也会沿作用域链向上找。其实完全不是一回事:this 是运行时绑定的执行上下文对象,由调用方式决定(如 obj.method() 中 this 是 obj),而作用域链只管标识符(变量名、函数名)的解析。
-
function f() { console.log(x); }查x→ 走作用域链 -
function f() { console.log(this.x); }查this.x→ 先确定this是谁,再用属性访问规则(原型链)找x - 箭头函数没有自己的
this,但它也不是靠作用域链“找”来的——它是直接继承外层函数的this值,属于绑定行为,不是查找行为
真正容易被忽略的是:作用域链只在**变量标识符解析阶段**起作用,一旦进入属性访问(obj.prop)、with 语句或 eval,就脱离了这条链。这些机制现在基本不用,但理解它们的边界,反而更能看清词法作用域的“纯粹性”。










