← 返回目录

1. var / let / const 对比

先打个比方:声明变量就像往抽屉里放东西并贴个标签var 是那种没规矩的旧抽屉——标签能随便重贴、东西能从抽屉缝里漏到外面;let 是"可换标签的箱子"——东西能换,但标签不能重名、箱子外碰不到里面;const 是"贴了封条的箱子"——贴好就不许再动。记住一句话:平时默认用 const,需要改值才用 let,var 基本别再用了

1.1 互动演示(点按钮看差异)

点击上方按钮,看每个关键字的真实表现。

1.2 对比表格(必背)

对比项var(旧抽屉)let(可换标签箱子)const(贴封条箱子)
作用域函数作用域(只认函数,不认花括号)块级作用域(认花括号 {})块级作用域(认花括号 {})
重复声明可以(后写的把先写的盖掉)不可以(直接报错)不可以(直接报错)
变量提升有(先能用、值是 undefined)没有(用之前必须先声明,报错)没有(用之前必须先声明,报错)
声明必须赋值不用(不写就是 undefined)不用必须赋初始值
值能否修改不能再指向别的东西(对象内部属性例外)

一句话抓重点:var 只认函数这堵墙,let/const 连一对花括号 {} 都能当墙。如果你在 for 循环里用 var 声明 i,循环结束后这个 i 会"漏"到外面变成同一个;换成 let,每次循环都给你一个新 i——这是面试最爱考的坑。

1.3 知识点讲解

① var:同一个标签能贴好几次,东西还会漏到外面

var 就是那个"旧抽屉":你贴了一张写着 a 的纸条,嫌不够再贴一张,它也不拦你,只是后一张把前一张盖住。

var a = 1; // 第一次贴标签 a,里面放 1
var a = 2; // 又贴一次 a!var 不报错,直接把里面换成 2
a = 3; // 改值,当然也行
// 坑:大项目里手滑把同名变量又写了一遍,原数据被悄悄盖掉,查 bug 查到崩溃

② let:东西能换,但标签不许重名

let 是"可换标签的箱子":箱子里的东西可以拿出来换新的(改值),但你不能再拿来一张写着同样名字的标签贴上去(重复声明会报错)。

let b = 1; // 声明 b,放 1
b = 2; // 允许!把里面换成 2(改值)
// let b = 3;   // ★ 报错:重复声明(SyntaxError)—— 同一张标签不许贴两次

let 的脾气:改值随便来,重名没门。日常"会变的量"就用它——比如计数器、循环里的临时结果。

③ const:贴了封条,声明时就得放好,之后不许动

const 是"贴了封条的箱子":你贴封条的时候必须把东西放进去(不能空着贴),贴好之后整个箱子不许再换、不许再打开

const c = 1; // 贴封条,同时放好 1
// c = 2;       // ★ 报错:不能改(TypeError)——封条动不了
// const d;      // ★ 报错:必须赋初始值 —— 不许空箱子贴封条
const user = { name: "大喇叭" }; // const 锁的是这个变量指向哪个对象
user.name = "小王"; // 不报错!const 锁的是"这个箱子不许换",箱子里的东西随便改

最容易踩的坑:以为 const 写的对象就完全不能改。如果你写 const user = {...} 然后去改 user.name,程序不会拦你——因为 const 封的是"这个变量不能再指向别的箱子",箱子里面那层抽屉你还是能拉出来换东西的。真要连内部一起锁死,才需要用 Object.freeze

④ 块级作用域:一对花括号就是一道墙

"作用域"就是"这个名字在哪些地方能被找到"。var 不认花括号这道墙,let/const 认。

// var:花括号拦不住,东西会漏到外面
{                          // 一对花括号,var 不认这道墙
  var x = 1; // var 声明,塞进花括号里
}                          // 花括号结束
console.log(x); // 1 —— 漏出来了!外面照样能用

// let / const:花括号就是一堵墙,墙外面摸不到
{                          // 一对花括号,let 认这道墙
  let y = 1; // 塞进花括号里
}                          // 出了花括号 y 就消失
// console.log(y); // ★ 报错:y is not defined —— 墙外面找不到

⑤ 面试高频题:for 循环里用 var 的坑(闭包陷阱)

想象你循环 3 次,想给 3 个按钮各绑定一个"打印自己序号"的函数。用 var 时,3 个按钮其实共用同一个 i,等按钮真被点的时候循环早就跑完了,i 已经是 3。

// 用 var:3 个按钮被点时全都打印 3(i 是同一个变量)
for (var i = 0; i < 3; i++) {              // 用 var 声明 i,3 轮共用一个
  btn.onclick = function () { console.log(i); }; // 绑点击事件
}

// 用 let:分别打印 0、1、2(每一轮循环都新复制了一个 i)
for (let i = 0; i < 3; i++) {              // 循环 3 次,每轮新 i
  btn.onclick = function () { console.log(i); }; // 各自记住当轮的 i
}

如果你在循环里用 var 绑定事件,点哪个按钮都打印最后那个值,因为 3 个回调闭包共享同一个 i。换成 let,每轮循环 JS 都会悄悄复制一份新的 i 给当前回调,点谁就是谁——这就是"闭包陷阱"的标准解法。

1.4 实战:用在哪 / 常见坑 / 怎么解决

① 可能在什么地方用:固定配置/常量(const 颜色值、接口地址)、循环里的索引(let i)、计数器/状态变量(let count);新项目里基本不再用 var

② 常见的问题:for 循环里用 var 绑事件,点哪个都打最后一个值;以为 const 对象完全不能改;声明 const 忘了赋初值直接报错。

③ 解决思路:循环绑事件一律 let i;const 对象内部属性照改,要锁死用 Object.freezeconst 必须声明时赋值;新代码默认 const,要改值再换 let

一句话:默认 const、要改值换 letvar 别再用。判断顺序:先问自己"这个值以后会变吗"——不会变就 const,会变就 let。