JavaScript只采用静态(词法)作用域,函数作用域在定义时确定,变量查找依赖作用域链而非调用栈;如test()在bar中调用却输出全局val值1,而非bar内val值2,印证其静态性。

JavaScript 中没有动态作用域,它只采用静态(词法)作用域。所谓“理解动态作用域”,其实是用来对比和反衬 JavaScript 作用域机制的一种教学方式——不是为了在 JS 中使用它,而是帮你更清楚地认出:为什么 函数里访问的变量,永远取决于它写在哪,而不是在哪被调用。
动态作用域的核心特征
动态作用域指:函数执行时,变量查找依赖于「调用栈」,即从当前函数开始,顺着调用它的上层函数作用域一层层向上找,直到找到第一个匹配的变量声明。
- 变量绑定发生在运行时,由控制流决定
- 同一个函数,在不同调用位置可能输出不同值
- 它不关心函数定义在哪,只关心“谁调了我”
用一个例子看清区别
看这段代码:
let val = 1;
function test() { console.log(val); }
function bar() { let val = 2; test(); }
bar(); // 输出 1,不是 2
如果 JS 是动态作用域,test() 在 bar 内部被调用,就会去 bar 的作用域找 val,输出 2;但实际输出 1,说明它查的是定义时的外层(全局),这就是静态作用域的铁证。
立即学习“Java免费学习笔记(深入)”;
为什么容易混淆?
有些场景看似“像”动态作用域,其实是误解:
-
this 绑定:虽然
this的值确实随调用方式变化(如obj.fn()vsfn()),但它和作用域无关,属于执行上下文的一部分 - 闭包捕获的是定义时的环境:哪怕函数返回后在别处执行,仍访问原始词法环境,这反而强化了静态性
- eval 或 with:它们会临时改变作用域链,但属于危险且已被严格限制的特例,不构成语言层面的动态作用域
哪些语言真用动态作用域?
真正支持动态作用域的语言极少,历史上有:
- Emacs Lisp(默认)、Common Lisp(可选)
- 早期 Shell 脚本(如 Bash 的局部变量行为)
- Perl(通过特殊声明启用)
而 JavaScript、Python、Java、C++、Go 等主流语言全部采用静态作用域——这是现代工程实践更可控、更易推理的选择。


















