词法作用域在函数定义时确定,与调用位置无关。验证一:全局定义的bar在foo内调用仍访问全局x;验证二:inner在outer内定义,形成闭包访问outer的x;验证三:let循环每次迭代创建新绑定,箭头函数定义时捕获对应j值;验证四:跨文件调用logmsg仍访问a.js中的msg。

词法作用域在函数定义时就确定,不是靠“感觉”或“调用位置”判断的,而是可以通过几段对比鲜明的代码直接验证。关键思路是:让同一个函数在不同嵌套层级中被调用,但它的定义位置始终不变,观察它访问的是哪个变量。
验证一:调用位置 ≠ 作用域来源
看这个例子:
let x = 'global';function bar() {
console.log(x);
}
function foo() {
let x = 'local';
bar(); // 在 foo 内部调用 bar
}
foo(); // 输出 'global',不是 'local'
bar 函数写在全局作用域里,所以它的外层词法环境就是全局。哪怕它在 foo 里被调用,也完全看不到 foo 内部的 let x —— 因为作用域链在 bar 被解析那一刻就定死了:全局 →(没有更外层)。
验证二:嵌套定义决定访问权限
再看嵌套定义的情况:
function outer() {let x = 'outer';
function inner() {
console.log(x); // 访问的是 outer 的 x
}
return inner;
}
const fn = outer();
fn(); // 输出 'outer'
inner 是在 outer 函数体内部写的,所以它的[[Environment]]在创建时就绑定了 outer 的词法环境。即使 outer 执行结束、变量本该“消失”,fn 仍能读到 x —— 这就是闭包,也是词法作用域最直观的证据。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
验证三:块级作用域中 let 的行为
for 循环配合 setTimeout 是经典验证场景:
// 使用 varfor (var i = 0; i setTimeout(() => console.log('var:', i), 10);
} // 输出两个 2
// 使用 let
for (let j = 0; j setTimeout(() => console.log('let:', j), 10);
} // 输出 0, 1
var 声明提升到函数作用域顶层,所有回调共享同一个 i;而 let 每次迭代都生成新的绑定,每个箭头函数在定义时就捕获了当前轮次的 j 值 —— 这个“捕获”动作发生在定义时刻,正是词法作用域的体现。
验证四:跨文件/执行上下文也不影响
哪怕把函数定义和调用拆开,只要定义位置没变,作用域链就不变:
// a.jsconst msg = 'from a';
export function logMsg() {
console.log(msg);
}
// b.js
import { logMsg } from './a.js';
const msg = 'from b';
logMsg(); // 依然输出 'from a'
logMsg 的定义在 a.js 的顶层,它的词法外层就是 a.js 的模块作用域。b.js 里声明的 msg 对它毫无影响 —— 引擎不会因为 import 或调用发生在 b.js 就去那里找变量。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










