javascript使用词法作用域,即函数作用域在定义时确定,变量查找依据代码结构而非调用位置;bash和emacs lisp则采用动态作用域,变量值取决于调用时的上下文环境。

JavaScript 使用词法作用域,而 Bash 和 Emacs Lisp 分别代表了动态作用域的典型实践场景。理解它们的差异,关键不在“谁对谁错”,而在“变量查找依据什么时机和路径”。
词法作用域:看函数写在哪,不看它在哪被调用
在 JavaScript 中,函数的作用域链在定义时就固化了。比如:
let x = "global";function bar() { console.log(x); }
function foo() {
let x = "local";
bar(); // 调用位置在 foo 内,但 bar 定义在全局
}
foo(); // 输出 "global"
bar 无论被谁调用、在哪调用,都只沿自己定义处的外层(全局)找 x —— 这就是词法作用域的确定性。
动态作用域:看函数在哪被调用,不看它写在哪
Bash 是动态作用域的常见例子。变量查找依赖调用栈,而非定义位置:
foo() { echo $value; }
bar() { local value=2; foo; }
bar # 输出 2
这里 foo 本身没定义 value,但它在 bar 内被调用,而 bar 刚好声明了一个 local value,所以 foo 就“看到”了 bar 的 value。查找路径是:foo 自身 → bar(调用者)→ 全局。定义位置完全不影响结果。
Emacs Lisp:支持动态作用域(可选),但默认已转向词法
老版本 Emacs Lisp 默认使用动态作用域,行为类似 Bash:
(setq x 10)(defun foo () x)
(defun bar () (let ((x 20)) (foo)))
(bar) ; 返回 20(不是 10)
但现代 Emacs(24.1+)默认启用词法作用域(需 (setq lexical-binding t)),此时 bar 中的 (let ((x 20)) ...) 不会影响 foo 对 x 的访问 —— foo 只认它定义时所在环境的 x(即全局的 10)。这种切换说明:动态作用域虽灵活,但可预测性弱;词法作用域更利于静态分析、重构与闭包实现。
为什么 JavaScript 坚持词法作用域?
- 闭包必须依赖定义时的环境快照,动态作用域无法稳定捕获外部变量
- 工具链(如 ESLint、TypeScript)能准确推导变量来源,提升开发体验
- 避免运行时行为因调用上下文突变而难以追踪(尤其在异步或高阶函数中)
词法作用域让代码意图更贴近所见即所得;动态作用域则把变量绑定推迟到运行时,适合脚本快速原型或局部临时覆盖,但代价是调试成本升高。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











