在全局作用域中,使用 var a = 1 会在 window 对象上挂载属性 a,但 let b = 1 却不会。请解释这是为什么?
核心答案
这是因为 var 和 let 在全局作用域中的绑定机制不同:
var在全局作用域中声明的变量会成为全局对象(window)的属性,这是因为var声明的变量存储在 Global Environment Record 的 Object Environment Record 中,该记录的绑定对象就是window。let在全局作用域中声明的变量存储在 Global Environment Record 的 Declarative Environment Record 中,这是一个独立的词法环境,与window对象无关。
深入解析
ECMAScript 规范中的 Global Environment Record
根据 ECMAScript 规范,全局环境记录(Global Environment Record)是一个复合结构,由两部分组成:
-
Object Environment Record:绑定对象为全局对象(浏览器中是
window)。var声明和function声明的标识符存储在这里,因此会自动成为window的属性。 -
Declarative Environment Record:独立的词法环境,不与任何对象绑定。
let、const、class声明的标识符存储在这里,因此不会出现在window上。
为什么要这样设计?
- 历史兼容性:
var挂载到window是 ES3 时代就有的行为,大量旧代码依赖window.xxx来访问全局变量,不能破坏这一行为。 - 块级作用域的需要:
let/const引入了块级作用域,它们需要一种新的存储机制来支持 TDZ(暂时性死区)等特性。如果仍然挂载到window上,就无法实现这些语义。 - 避免全局污染:不挂载到
window可以减少全局命名空间的污染,也避免了意外覆盖window上已有属性的风险。
常见误区
- 误区一:认为
let声明的变量"不是全局变量"。实际上它仍然是全局作用域的变量,只是不作为window的属性存在。 - 误区二:认为
let变量存储在某个"隐藏对象"上。实际上 Declarative Environment Record 是引擎内部的数据结构,不对应任何 JS 可访问的对象。 - 误区三:认为
this === window时可以通过this.b访问let b。不行,let声明的变量与this(全局对象)完全无关。
代码示例
// var 声明 —— 挂载到 window
var a = 1;
console.log(window.a); // 1
console.log('a' in window); // true
// let 声明 —— 不挂载到 window
let b = 2;
console.log(window.b); // undefined
console.log('b' in window); // false
// 但 b 确实是全局变量,可以直接访问
console.log(b); // 2
// function 声明也会挂载到 window(和 var 行为一致)
function foo() {}
console.log(window.foo); // ƒ foo() {}
// const 和 let 行为一致
const c = 3;
console.log(window.c); // undefined
// 直接赋值(不声明)会挂载到 window
// 这是因为它被视为对全局对象的属性赋值
d = 4; // 非严格模式下
console.log(window.d); // 4
// 一个实际的坑:覆盖 window 已有属性
var name = 123;
console.log(window.name); // "123" (注意:window.name 会强制转为字符串)
console.log(typeof name); // "string"
let name2 = 123;
console.log(window.name2); // undefined
console.log(typeof name2); // "number" —— 不受 window.name 的 setter 影响
面试技巧
面试官可能的追问方向:
-
"那
let声明的全局变量存在哪里?" → 回答 Global Environment Record 中的 Declarative Environment Record,是引擎内部结构,无法通过 JS 代码直接访问。 -
"TDZ(暂时性死区)和这个有关系吗?" → 有关系。正是因为
let/const使用 Declarative Environment Record,引擎可以精确追踪变量是否已初始化,从而实现 TDZ。 -
"在 Node.js 中
var会挂载到global吗?" → 不会。Node.js 中每个文件都被包裹在一个模块函数中,var声明的变量属于该函数作用域,不是真正的全局作用域。 -
"ES Module 中的
var呢?" → ES Module 有自己的模块作用域,var不会挂载到window。
一句话总结
var挂载到window是因为它绑定在 Global Environment Record 的 Object Environment Record(绑定对象为window)中,而let/const绑定在独立的 Declarative Environment Record 中,与window无关。