JavaScript 只有词法作用域,函数定义时即确定可访问变量;词法作用域由源码嵌套结构静态决定,与调用位置无关;eval 和 with 破坏静态性;箭头函数不创建新词法环境,直接复用外层。
JavaScript 只有词法作用域(Lexical Scope),没有动态作用域。所谓“解释词法作用域”,本质是理解函数在定义时就确定了能访问哪些变量,而不是调用时才决定。
词法作用域意味着作用域链在函数被声明(parse 阶段)时就已静态确定,和函数在哪里被调用完全无关。JS 引擎通过分析源码的嵌套结构生成作用域链。
function 和 const/let 声明都会创建新的词法环境function outer() {
const x = 'outer';
function inner() {
console.log(x); // ✅ 能访问,因为 inner 定义在 outer 内部
}
return inner;
}
const fn = outer();
fn(); // 输出 'outer' —— 不是在 outer 内调用,但依然能读 x
动态作用域不存在于标准 JavaScript 中,但它是一种理论对比模型:变量解析依赖函数实际的调用位置,而非定义位置。常见于某些 shell(如 bash)或早期 Lisp 方言。
this 绑定机制常被误认为是动态作用域,但它只影响 this,不影响词法变量(var/let/const)// 假设 JS 支持动态作用域(实际不支持):
function foo() { console.log(x); }
function bar() { const x = 'bar'; foo(); }
function baz() { const x = 'baz'; foo(); }
bar(); // 输出 'bar'
baz(); // 输出 'baz'
// 但现实中,这段代码会报 ReferenceError: x is not defined
eval 和 with 是 JS 中仅有的两个能“运行时修改词法环境”的特性,它们让引擎无法在编译期确定所有变量绑定,从而影响优化和调试体验。
eval('const y = 1') 可能在当前作用域注入新变量,导致 V8 等引擎禁用某些
优化(如内联缓存)with (obj) { prop } 让属性访问变成运行时解析,无法静态推导 prop 指向哪个对象with 被禁止,eval 也被限制在独立作用域中(不污染外层)箭头函数没有自己的 this、arguments、super 或 new.target,但它对词法作用域的遵循比普通函数更彻底——它连自己的词法环境都不创建,直接复用外层函数的。
[[Environment]],指向定义时的词法环境[[Environment]] 直接继承外层函数的,不新建环境arguments 实际是外层函数的,不是自己调用时的function outer() {
const x = 'outer';
return () => console.log(x); // ✅ 依赖 outer 的词法环境
}
// 箭头函数没自己的作用域,但词法作用域规则没变 —— 它只是更“懒”地复用了
真正容易被忽略的是:词法作用域不是靠“函数是否嵌套”来判断的,而是靠源码文本中函数表达式/声明出现的位置。哪怕函数是字符串拼接后 eval 出来的,它的词法环境也只取决于 eval 执行时所处的作用域,而不是字符串内容本身长什么样。