在 JavaScript 中,var 和 let 都是用于声明变量的关键字,但它们在作用域、提升行为、重复声明、全局对象属性等方面存在核心差异,这些差异直接影响代码的安全性和可维护性。以下是详细对比:
一、核心区别总览表
| 特性 | var | let |
|---|---|---|
| 作用域 | 函数作用域(function-scoped)或全局作用域 | 块级作用域(block-scoped) |
| 提升行为 | 变量提升(hoisting),初始值为 undefined | 暂时性死区(TDZ),提升但不可访问 |
| 重复声明 | 允许(覆盖原有变量) | 不允许(同一作用域内报错) |
| 全局对象属性 | 声明的全局变量会成为 window(浏览器)/global(Node)的属性 | 声明的全局变量不会成为全局对象的属性 |
| 循环中的表现 | 循环变量泄露为全局 / 函数变量,闭包易出错 | 循环变量为块级作用域,闭包表现符合预期 |
二、逐点详细解析
1. 作用域差异(最核心区别)
-
var:函数作用域 / 全局作用域var声明的变量仅在当前函数内有效,若在函数外声明则为全局作用域(无论是否在代码块内,如if、for块)。示例:
if (true) { var a = 10; // 函数/全局作用域,而非 if 块级作用域 } console.log(a); // 10(可访问,因为 a 不是块级变量) function fn() { var b = 20; } fn(); console.log(b); // ReferenceError: b is not defined(函数作用域,外部不可访问) -
let:块级作用域let声明的变量仅在当前代码块({}包裹的区域,如if、for、while、函数体)内有效,块外无法访问。示例:
if (true) { let a = 10; // 块级作用域(if 块内) } console.log(a); // ReferenceError: a is not defined(块外不可访问) for (let i = 0; i < 3; i++) { // i 是循环块级作用域,每次循环创建新的 i } console.log(i); // ReferenceError: i is not defined(循环块外不可访问)
2. 提升行为与暂时性死区(TDZ)
-
var:变量提升 + 初始化为undefinedvar声明的变量会被 “提升” 到当前作用域的顶部,且默认初始值为undefined,因此在声明前访问变量不会报错,只会得到undefined。示例:
console.log(x); // undefined(变量提升,已初始化) var x = 5; console.log(x); // 5等价于:
var x; // 提升到作用域顶部,初始化为 undefined console.log(x); // undefined x = 5; console.log(x); // 5 -
let:提升但进入暂时性死区(TDZ)let声明的变量也会被提升,但不会默认初始化,而是进入 “暂时性死区”(Temporal Dead Zone,TDZ)—— 从作用域开始到变量声明语句之间的区域,访问变量会直接报错。示例:
console.log(y); // ReferenceError: Cannot access 'y' before initialization(TDZ 报错) let y = 5; console.log(y); // 5✅ 好处:避免 “变量声明前使用” 的逻辑错误,强制开发者按顺序编写代码。
3. 重复声明
-
var:允许重复声明(覆盖原有变量)同一作用域内多次用var声明同一变量,不会报错,后续声明会覆盖前面的赋值。示例:
var a = 10; var a = 20; // 允许重复声明,覆盖原有值 console.log(a); // 20 -
let:不允许重复声明(同一作用域内报错)同一作用域内用let重复声明同一变量,会直接抛出语法错误(SyntaxError),无论之前是用var还是let声明的。示例:
let b = 10; let b = 20; // SyntaxError: Identifier 'b' has already been declared var c = 30; let c = 40; // 同样报错(同一作用域内重复声明)✅ 好处:避免意外覆盖变量,减少代码逻辑错误。
4. 全局对象属性
-
var:全局声明会成为全局对象的属性在全局作用域(函数外)用var声明的变量,会成为全局对象(浏览器中是window,Node.js 中是global)的属性,可通过全局对象访问。示例(浏览器环境):
var globalVar = "hello"; console.log(window.globalVar); // "hello"(可通过 window 访问) -
let:全局声明不会成为全局对象的属性在全局作用域用let声明的变量,不会挂载到全局对象上,仅在全局作用域内有效,无法通过全局对象访问。示例(浏览器环境):
let globalLet = "world"; console.log(window.globalLet); // undefined(不会成为 window 属性)✅ 好处:避免污染全局对象,减少全局变量冲突。
5. 循环中的表现(闭包场景)
这是 var 和 let 最常用的差异场景,尤其在循环中创建闭包时(如 setTimeout 回调)。
-
var:循环变量泄露 + 闭包共享同一变量var声明的循环变量是函数 / 全局作用域,所有循环内的闭包都会共享同一个变量(循环结束后变量的值是最终值),导致回调执行时拿到的都是最后一个值。示例(经典坑):
for (var i = 0; i < 3; i++) { setTimeout(() => { console.log(i); // 输出 3、3、3(所有回调共享同一个 i,循环结束后 i=3) }, 100); } console.log(i); // 3(循环变量泄露为全局变量) -
let:循环变量为块级作用域 + 每次循环创建新变量let声明的循环变量是块级作用域,且 JavaScript 引擎会为每次循环创建一个新的变量副本,闭包捕获的是每次循环的独立变量,因此回调执行时能拿到对应循环的正确值。示例(符合预期):
for (let i = 0; i < 3; i++) { setTimeout(() => { console.log(i); // 输出 0、1、2(每次循环的 i 是独立的) }, 100); } console.log(i); // ReferenceError: i is not defined(无变量泄露)✅ 好处:无需手动创建闭包(如 IIFE)来保存循环变量,代码更简洁、符合直觉。
三、使用建议
- 优先使用
let:let解决了var的三大问题(作用域混乱、重复声明、变量提升导致的逻辑错误),代码更安全、可维护。 - 避免使用
var:除非需要兼容非常老旧的浏览器(如 IE11 以下),否则不建议用var。 - 补充:
const:const与let特性一致(块级作用域、无重复声明、TDZ),仅多了 “声明时必须赋值且不可重新赋值” 的限制,适合声明常量(如配置、固定值)。
总结
var 和 let 的核心差异源于作用域模型的升级:let 引入块级作用域,修复了 var 导致的变量泄露、重复声明、闭包异常等问题,是 ES6 后推荐的变量声明方式。记住一句话:ES6 后,用 let 声明变量,用 const 声明常量,忘掉 var。

1258

被折叠的 条评论
为什么被折叠?



