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 方言。
- 查找变量时,沿着当前调用栈向上找,看哪一层的执行上下文里有该变量
- 同一个函数,在不同调用链中可能访问到不同的同名变量
- JS 的
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 破坏词法作用域静态性?
eval 和 with 是 JS 中仅有的两个能“运行时修改词法环境”的特性,它们让引擎无法在编译期确定所有变量绑定,从而影响优化和调试体验。
立即学习“Java免费学习笔记(深入)”;
-
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 执行时所处的作用域,而不是字符串内容本身长什么样。











