×

一文搞懂let、const、var 的区别是什么?

独孤求败 独孤求败 发表于2026-07-03 10:01:29 浏览21 评论0

抢沙发发表评论

面试高频题,也是日常写代码天天踩的坑。这玩意儿说简单也简单,说深了能聊到执行上下文。本文用最直白的方式,把六个核心差异点讲清楚。


总结

var是 ES6 之前长期使用的声明方式,在函数内是函数作用域、会提升、能重复声明。letconst是 ES6 引入的,块级作用域、存在暂时性死区、不可重复声明。const额外要求声明时必须初始化,且不能重新赋值。


1. 作用域:var 不认普通代码块,let/const 是块级作用域

这是最根本的区别。

// var — 函数作用域
function testVar() {
    if(true){
        var 1;
    }
    console.log(x);// 1 — if 块挡不住 var
}
// let/const — 块级作用域
function testLet() {
    if(true{
        let 1;
        const 2;
    }
    console.log(y);// ReferenceError: y is not defined
}

在函数内部,var只有函数体这个大括号才算"边界"。ifforwhile后面的花括号它统统不认。这也是经典坑for循环用var出问题的根源:

for(var i=0i<3i++{
    setTimeout(()=> console.log(i), 0);
}
// 输出:3 3 3 — 循环结束 i 已经变成 3 了
for(let j=0j<3j++{
    setTimeout(()=> console.log(j), 0);
}
// 输出:0 1 2 — 每次迭代都有独立的 j 绑定

一句话记:用 var 写 for 循环加 setTimeout,就是在给自己挖坑。

图片


2. 变量提升:var 可先访问,let/const 会进入 TDZ

三种声明都会在作用域创建阶段被引擎登记,这通常被叫作"变量提升"。但它们的初始化时机不同,所以访问行为完全不同。

console.log(a);// undefined — 提升并默认初始化为 undefined
var 1;
console.log(b);// ReferenceError: Cannot access 'b' before initialization
let b = 2;

var的变量提升你肯定见过——变量还没声明就能访问,值是undefined。准确说,不是引擎真的把代码搬到了顶部,而是在创建作用域时先登记了a,并初始化为undefineda = 1仍然在原来的位置执行。

letconst也会在作用域创建阶段被登记,但在执行到声明语句前不会完成初始化。从作用域开始到声明行之间的这段区域,就叫暂时性死区。在这个区域里访问变量,直接抛错。

一句话记:声明之前访问变量,var 给你 undefinedlet/const 给你报错。


3. 暂时性死区(TDZ)

这个面试常考,单独拿出来讲。

{
    // 这个块作用域的 TDZ 从这里开始
    console.log(typeof foo);// ReferenceError(不是 undefined!)
    let foo 1;
    // TDZ 在这里结束
}

坑人的地方在于,用typeof检查一个没声明的变量本来会返回"undefined",但如果这个变量在当前作用域里用let声明了(只是还没执行到那一行),typeof同样会报错。因为这个绑定已经在作用域创建阶段登记了,只是还在死区里。

// 变量根本没声明过
console.log(typeof neverDeclared);// "undefined" — 安全// 变量声明了但还没到
{
    console.log(typeof willBeDeclared);// ReferenceError — TDZ!
    let willBeDeclared 1;
}
图片


4. 重复声明:var 可以,let/const 不行
var 1;
var a = 2;// 没问题,后一次赋值覆盖前一次值

下面两个错误示例要分别运行:

let 1;
let b = 2;// SyntaxError: Identifier 'b' has already been declared
const 1;
const c = 2;// SyntaxError: Identifier 'c' has already been declared

var允许在同一个作用域里重复声明同一个变量名,如果后一次声明带初始化赋值,就会覆盖前面的值。这在大型文件里容易造成变量污染——你可能不小心覆盖了别人前面定义的变量,还找不到 bug。

letconst在同一作用域里重复声明会直接报错,逼你在声明时就想清楚。

注意:在同一个作用域里,varlet混着声明同名变量也不行:

var 1;
let x = 2;// SyntaxError

但如果是嵌套块作用域,let可以遮蔽外层的var

var 1;
{
    let 2;// OK,块作用域里的新变量
}


5. const 的特殊规则

constlet在作用域、TDZ、重复声明规则上基本一样,但多了两条限制:

第一,必须声明时初始化。

let a;// OK,默认 undefined
const b;// SyntaxError: Missing initializer in const declaration

第二,不能重新赋值。

const PI 3.14;
PI = 3;// TypeError: Assignment to constant variable

但这里有个高频误区:const 约束的是变量绑定:这个名字不能再绑定到另一个值;它不负责冻结对象本身。

// 基本类型 — 不能重新赋值
const num 1;
num 2;// TypeError// 引用类型 — 默认情况下,内部内容可以改
const arr [1,2,3];
arr.push(4);// 完全没问题
arr[0999;// 完全没问题
arr [5,6,7];// TypeError — 你在重新绑定到另一个数组,不行
const obj name:'Alice' };
obj.name 'Bob';// OK
obj.age 18;// OK
obj = { name: 'Cat' };// TypeError — 不能重新绑定到另一个对象

如果你想禁止对象第一层属性被改,可以用Object.freeze()

const obj Object.freeze({ name'Alice' });
obj.name = 'Bob';// 静默失败(严格模式下报错)

但要注意,Object.freeze()默认只冻结第一层:

const user Object.freeze({ 
    profilename'Alice' }
});
user.profile.name = 'Bob';// OK,嵌套对象没被冻结

如果要做深层不可变,需要递归冻结,或者使用专门的不可变数据方案。

一句话记:const 锁的是绑定,不是值。普通数组和对象的内部内容默认可以改;被冻结、属性不可写等情况除外。

图片


6. 全局作用域下的行为差异

在浏览器普通 script 的全局作用域下:

var globalVar 1;
let globalLet 2;
console.log(window.globalVar);// 1 — var 声明的变量挂到了 window 上
console.log(window.globalLet);// undefined — let 声明的变量不会

一句话记:在浏览器普通 script 里,var 会把全局变量挂到 window 上,let/const 不会。

注意,这条只针对浏览器普通 script。ES Module、Node.js 顶层作用域都不完全一样。


最佳实践

搞清楚区别之后,日常怎么写?

  1. 默认用 const

    大部分变量你并不会重新赋值,用const表明了"这个变量不会被重新赋值"的意图,代码可读性更高。
  2. 需要重新赋值时用 let

    比如循环计数器、累加器、需要 swap 的临时变量。
  3. 现代业务代码默认别用 var

    除非你在维护老代码、需要保持 ES5 兼容,或者必须跟旧项目风格一致。

// 日常推荐姿势
const name 'Alice';// 不会重新赋值
const items [1,2,3];// 不会重新绑定到别的数组
let count 0;// 需要改的值
for(let 0i<10i++){
    // 循环变量
    console.log(i);
}


一张图总结
图片

把这篇文章收藏起来,面试前扫一眼这张图就够了。


群贤毕至

访客