js var let const 三者有什么区别
var 为函数作用域、存在变量提升且可重复声明,let 和 const 为块级作用域、有暂时性死区且不可重复声明,const 还要求声明时立即赋值并禁止重新赋值;日常编码默认用 const,需重新赋值时用 let,var 仅见于旧代码维护。
**结论:var 是函数作用域、存在变量提升且默认值为 undefined、允许重复声明;let 和 const 是块级作用域、有暂时性死区(TDZ,声明前访问直接报错)、不允许重复声明;const 额外要求声明时立即赋值,且不能再重新赋值。** 日常写代码的默认选择是 const,需要重新赋值时才换成 let,var 基本只在维护老代码时才会见到。
var、let、const 最核心的区别是什么?
一句话:作用域范围、能否重复声明、能否重新赋值,这三条就区分完了。
| 维度 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 提升并初始化为 undefined | 提升但未初始化(TDZ) | 提升但未初始化(TDZ) |
| 重复声明 | 允许 | 报错 | 报错 |
| 必须初始化 | 否 | 否 | 是 |
| 重新赋值 | 可以 | 可以 | 报错(TypeError) |
| 挂到 window | 全局声明时会 | 不会 | 不会 |
「块级作用域」指 {}、if、for 这类大括号范围;「函数作用域」指只有函数体才形成边界。具体看这段代码:
{ var a = 1; let b = 2; const c = 3; }
console.log(a); // 1
console.log(b); // ReferenceError: b is not defined
let/const 在 ES6(ECMAScript 2015,2015 年 6 月发布)引入,这也是它们和 var 分家的起点。
什么是变量提升和暂时性死区(TDZ)?
结论:var 在声明前访问得到 undefined,let/const 在声明前访问直接抛 ReferenceError,这段「声明语句之前、作用域之内」的区间就叫暂时性死区。
console.log(x); // undefined
var x = 1;
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 2;
很多人以为 let 没有提升,其实引擎在进入块时就已经为 y 分配了空间,只是不初始化,直到执行到声明那一行才填值。区别在于 var 被「提前初始化成 undefined」,所以访问不报错但拿不到值——这正是老代码里 undefined 报错的常见来源。TDZ 的好处是把「用错变量」从静默的 undefined 变成显式报错。
var 和 let 在 for 循环里有什么区别?
结论:for (var i = 0; ...) 全局只有一个 i,循环结束后 i 等于循环次数;for (let i = 0; ...) 每轮迭代生成一个新的 i 绑定,因此 setTimeout 能拿到 0、1、2。
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3 3 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j)); // 0 1 2
这是面试和实际调试中出现频率最高的差异。ES6 之前要修这个问题,得用 IIFE(立即执行函数)把 i 作为参数包一层,现在直接用 let 就够了。
const 声明的对象为什么还能改属性?
结论:const 锁的是「变量与值之间的绑定」,不是值本身。对象是引用类型,只要引用没换,改内部属性完全合法。
const obj = { a: 1 };
obj.a = 2; // 合法
obj.b = 3; // 合法
obj = {}; // TypeError: Assignment to constant variable.
const arr = [1, 2];
arr.push(3); // 合法
如果确实要连属性一起锁死,用 Object.freeze(obj)(浅冻结)或 Object.freeze 加递归实现深冻结;注意冻结后严格模式下赋值会报 TypeError,非严格模式静默失败。另外 const 声明必须带初始值,const x; 会直接抛 SyntaxError: Missing initializer in const declaration。
实际开发中该怎么选?
结论:默认 const,需要重新赋值时改 let,除非在改一个已经大量使用 var 的老文件,否则不要新写 var。
三条可执行规则:
- 能用 const 就用 const,包括对象、数组、函数表达式,因为它能防止误赋值。
- 只有循环计数器、累加器这类会被重新赋值的变量用 let。
- 打开 ESLint 的
prefer-const和no-var规则,让工具替你做这件事。
需要注意的是,var 在浏览器非模块脚本的顶层声明会挂到 window 上(var a = 1 后 window.a === 1),let/const 不会,所以用 let 声明同名变量不会污染全局对象。这个差别在老库和现代打包代码混用时经常成为排查问题的关键点。
回到开头那句话:作用域、TDZ、重复声明、重新赋值,四个点记住了,var/let/const 的区别就再也不会混。
转载请注明出处,版权归原作者所有。