前言:为什么我们要告别 var?
**
早期 JS 仅用于简单的页面交互,设计上存在不少 "反直觉" 的坑 ------var 就是典型代表。直到 ES6(2015)推出 let 和 const,才真正解决了 var 的缺陷,让 JS 更适合企业级开发。今天我们就从底层原理到实际用法,彻底理清这三者的区别,以及那些容易踩坑的细节。
一、JS 变量声明的演进:从 ES5 到 ES6
1. ES5 的唯一选择:var
var 是 ES5 中唯一的变量声明方式,但它的特性常常让人困惑:
- 弱类型:变量类型由值决定,可随意修改(比如先存数字再存字符串)
- 无块级作用域:只认函数作用域和全局作用域,块({})内声明的变量会 "泄露" 到外部
- 变量提升(Hoisting) :编译阶段就会声明变量,但值为 undefined,执行阶段才赋值
看个直观的例子(来自你的笔记):
            
            
              ini
              
              
            
          
          // 反直觉场景:变量还没声明,却能访问到
console.log(age); // 输出 undefined(不是报错!)
var age; // 编译阶段已"偷偷"声明,值为 undefined
age = 18; // 执行阶段才赋值
console.log(age); // 18
// 常量的"伪约定":大写变量只是习惯,实际能修改
var PI = 3.1415926;
PI = 3.15; // 不报错!
console.log(PI); // 3.15这里的核心问题是 变量提升破坏了 "先声明后使用" 的逻辑,降低了代码可读性,还容易引发变量污染(比如不同块内重名的 var 变量会互相覆盖)。
2. ES6 的改进:let 和 const
为解决 var 的缺陷,ES6 新增了 let(声明变量)和 const(声明常量),它们的核心特性是:
- 支持块级作用域
- 不存在 "变量提升" 的直觉问题(实际会提升,但进入 "暂时性死区")
- const 声明的常量必须初始化,且不能修改引用(简单类型不可改,复杂类型可改属性)
二、核心区别:一张表 + 代码案例
| 特性 | var | let | const | 
|---|---|---|---|
| 块级作用域 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | 
| 变量提升 | ✅ 声明提升(值为 undefined) | ✅ 提升但进入 "暂时性死区" | ✅ 提升但进入 "暂时性死区" | 
| 重复声明 | ✅ 允许 | ❌ 不允许 | ❌ 不允许 | 
| 初始化要求 | ❌ 可选 | ❌ 可选 | ✅ 必须初始化 | 
| 值可修改 | ✅ 允许 | ✅ 允许 | ❌ 简单类型不可改;复杂类型不可改引用 | 
1. 块级作用域:let/const 解决 "变量泄露"
var 无视 {} 块级作用域,而 let/const 会被块级作用域限制:
            
            
              arduino
              
              
            
          
          {
  var age = 18;    // var 不支持块级作用域
  let height = 188;// let 支持块级作用域
}
console.log(age);   // 18(变量泄露到外部)
console.log(height);// ReferenceError: height is not defined(块外无法访问)这是 var 最常见的坑之一 ------ 比如在 for 循环中用 var 声明计数器,循环结束后变量仍会污染全局。
2. 暂时性死区(TDZ):let/const 避免 "提前访问"
var 会提前声明导致 "未定义却能访问",而 let/const 虽然也会在编译阶段提升,但会进入 "暂时性死区":在声明前访问,直接报错,强制遵循 "先声明后使用"。
            
            
              ini
              
              
            
          
          // var 的情况:提前访问不报错,输出 undefined
console.log(age); // undefined
var age = 18;
// let/const 的情况:提前访问直接报错
console.log(PI);  // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;
console.log(height); // ReferenceError: Cannot access 'height' before initialization
let height = 188;3. const 的 "常量" 误区:不是所有值都不能改!
const 声明的 "常量",核心限制是 "不能修改引用地址" ,而非 "值不能改":
- 简单类型(数字、字符串、布尔):值存在栈中,引用即值,所以不能改
- 复杂类型(对象、数组):值存在堆中,引用是堆地址,所以 属性 / 元素可改,但不能重新赋值引用
看案例(来自你的笔记):
            
            
              ini
              
              
            
          
          const person = {
  name: "ysw",
  age: 28
};
// 允许:修改对象属性(引用地址没变)
person.age = 21;
console.log(person); // { name: "ysw", age: 21 }
// 不允许:重新赋值引用(地址变了)
person = "hahaha"; // TypeError: Assignment to constant variable.如果想让对象 "完全不可改",可以用 Object.freeze() 冻结对象(浅冻结,深冻结需递归处理):
            
            
              ini
              
              
            
          
          const frozenPerson = Object.freeze(person);
frozenPerson.age = 22; // 严格模式下报错,非严格模式下修改无效
console.log(frozenPerson.age); // 21(值未变)三、常见报错及解决方案
实际开发中,关于 var/let/const 的报错主要有 3 类,对应解决办法很明确:
| 报错信息 | 原因 | 解决方案 | 
|---|---|---|
| ReferenceError: xxx is not defined | 块级作用域外访问 let/const 变量 | 在块内使用,或把声明提到块外 | 
| TypeError: Assignment to constant variable | 试图修改 const 变量的值 / 引用 | 改用 let 声明,或不修改 const 引用 | 
| ReferenceError: Cannot access 'xxx' before initialization | 暂时性死区:声明前访问 let/const | 确保先声明,再使用 | 
四、补充:函数提升 vs var 提升
你的笔记中提到 "函数是一等公民",这里需要区分 函数提升 和 var 提升 的差异:
- 两者都会提升到作用域顶部
- var 只提升 "声明",不提升 "赋值"(所以提前访问是 undefined)
- 函数声明(function xxx() {})会同时提升 "声明" 和 "赋值"(所以提前调用能执行)
案例:
            
            
              javascript
              
              
            
          
          // 函数提升:提前调用能执行(声明+赋值都提升了)
setWidth(); // 输出 100
function setWidth() {
  var width = 100;
  console.log(width);
}
// var 提升:提前访问是 undefined(只提升声明,不提升赋值)
console.log(getHeight); // undefined
var getHeight = function() {
  return 188;
};五、最佳实践:该用 let 还是 const?
- 彻底告别 var:var 的缺陷太多,ES6 后完全没必要再用
- 优先用 const:只要变量的值 / 引用不需要修改,就用 const(比如函数参数、固定配置、对象 / 数组),能减少意外修改,提升代码稳定性
- 按需用 let:只有当变量需要重新赋值时(比如循环计数器、动态变化的状态),才用 let
- 冻结敏感对象:如果 const 声明的对象不希望被修改属性,记得用 Object.freeze()(深冻结需额外处理)
总结
var 是 JS 早期设计的产物,存在变量提升、无块级作用域等缺陷;let 和 const 则是 ES6 对变量声明的优化,解决了 var 的痛点,还通过 "暂时性死区" 强制规范代码逻辑。
记住核心原则:不用 var,优先 const,let 按需用,再结合对 "引用类型" 和 "块级作用域" 的理解,就能避免 99% 关于变量声明的坑~