【JS 基础】告别 var!一文搞懂 let、const 与作用域避坑指南 🚀
前言:
今天想要写这篇有关var_let_const的文章主要是每天学习完热乎乎的知识时,过一段时间后没回顾就凉了,而且有时候不想听课,没事就看看jym写的前端面经,发现这个基础知识点常问,看似很简单,自己脑子里想一遍,回答起来又觉得磕磕绊绊,所以就通过写文章来对知识进行一个梳理,也是对自己的督促吧这个知识点经常作为一个基础知识点主要是ES5->ES6的过程中,对变量的声明有较大的变化(就像高中考作文,主题总是与时事有关,面试官也喜欢问新技术,技术更新,技术迭代方面的)。
今天,我们就结合几个经典的实战案例,彻底搞懂这三者的区别,顺便聊聊如何实现真正的“不可变对象”。
首先对var、let、const、暂时性死区进行一个介绍
var:ES5 时代的变量声明方式,存在变量提升且无块级作用域,并可重复声明,但容易造成全局污染,可读性差。
let:ES6 新增的块级作用域变量,允许修改值,不可重复声明,并严禁在声明前使用。
const:ES6 新增的常量声明,锁定的是变量的内存地址(即简单类型值不可变,对象属性可变),声明时必须赋值,并且声明后不可修改。
暂时性死区 (TDZ) :在代码块内,从作用域顶部到 let 或 const 语句声明完成前的这段区域,访问该变量会直接报错。
🚀 ES6 新特性初体验
在 ES6(ECMAScript 2015)出现之前,JavaScript 的世界里只有 var。由于 JS 是弱类型语言,变量的类型完全由当前的值决定,这虽然灵活,但也带来了不少隐患。
ES6 引入了 let 和 const,标志着 JS 变量声明进入了更规范的时代。
看看这段基础代码(源自笔记 1.js):
JavaScript
// JS 是弱类型语言,类型由值决定
var age = 18;
age++;
console.log(age); // 19
// ❌ 历史遗留:ES5 只有 var,没有常量概念
// ✅ 现代标准:ES6 新增 let 和 const
// let 用于声明变量(Block Scoped)
let name = '张三';
let height = 1.88;
height++;
console.log(height); // 2.88
// const 用于声明常量
const key = '123456';
console.log(key);
💡 核心区别初探:
- var:老旧标准,存在变量提升,无块级作用域。
- let:现代标准,可变变量,支持块级作用域。
- const:现代标准,常量(引用的内存地址不可变),声明时必须赋值。
👻 诡异的变量提升 (Hoisting)
你是否遇到过代码没报错,但打印出来却是 undefined 的情况?这通常是 var 的“变量提升”在作祟。
请看案例(源自笔记 2.js):
JavaScript
// ❌ 糟糕的体验:var 声明被提升了
console.log(age); // 输出 undefined,而不是报错,因为在打印前age没有被初始化
var age = 18;
// ✅ 严谨的体验:let 必须先声明后使用
// console.log(height); // 报错:ReferenceError
let height = 188;
原理解析:
在 JS 引擎的编译阶段,var age 的声明会被“提升”到当前作用域的顶部,但赋值操作 age = 18 依然留在原地。所以代码实际执行顺序变成了:
- var age; (默认 undefined)
- console.log(age);
- age = 18;
⚠️ 风险提示: 在大型项目中,这种特性会导致变量在未预期的情况下被使用,极难排查。而 let 和 const 存在“暂时性死区”(TDZ),强制要求先声明,后使用。
📦 作用域大作战:块级 vs 全局
ES6 最大的贡献之一就是引入了块级作用域(Block Scope) 。
1. 块级作用域(Block Scope)
以前用 var 写 for 循环或 if 判断时,变量会泄露到全局,污染环境。
对比代码(源自笔记 4.js):
JavaScript
{
// 块级作用域
var age = 18; // ❌ 不支持块级作用域,会穿透 {}
let height = 188; // ✅ 支持块级作用域,被限制在 {} 内
}
console.log(age); // 18 -> 变量泄露了!
// console.log(height); // 报错:height is not defined -> 安全隔离
2. 函数作用域与局部提升
函数一直都是 JS 中隔离变量的“安全屋”。但在函数内部,var 依然遵循函数级别的变量提升。
深度案例(源自笔记 5.js):
JavaScript
var width = 1000; // 全局变量
function setWidth() {
// 函数作用域
// 这里的 console.log 输出什么?
console.log(width);
var width = 100; // 局部变量声明
console.log(width);
}
setWidth();
🤔 思考: 为什么第一个 console.log(width) 输出的是 undefined 而不是全局的 1000?
答案: 还是变量提升!函数内部的 var width 被提升到了函数体顶部,覆盖了全局变量的引用,但此时尚未赋值。
🤔 思考: 而为什么第二个console.log(width)输出的是100?
答案: 因为在第二个console.log(width)前width已经被声明了。
🔒 const 的“谎言”与真相
const 代表常量,很多人认为用了 const 变量就永远不会变了。其实,这里有一个常见的误区。
真相是:const 保证的是栈内存中的“值”或者是“内存地址”不可变。
- 对于简单数据类型(Number, String...),值不可变。
- 对于复杂数据类型(Object, Array...),地址不可变,但堆内存中的数据可以变!
实战破解(源自笔记 3.js):
JavaScript
const PI = 3.1415926;
// PI = 3.15; // ❌ 报错:Assignment to constant variable.
const person = {
name: '张三',
age: 18
};
// 😱 为什么 const 声明的对象可以修改?
person.age = 20;
console.log(person); // { name: '张三', age: 20 } -> 修改成功!
// person = {}; // ❌ 报错:不能修改 person 指向的内存地址
进阶:如何实现真正的“对象不可变”?
如果你希望一个对象像铁板一块,完全不能被修改,可以使用 ES5 提供的 Object.freeze() 方法。
JavaScript
// 🧊 冻结对象
const wes = Object.freeze(person);
wes.age = 30; // 非严格模式下静默失败,严格模式下报错
console.log(wes.age); // 依然是 20
注意:Object.freeze() 只能冻结一层(浅冻结)。如果对象内部还有对象,需要递归冻结。
📝 最佳实践与总结
经过上面的分析,我们可以得出 clear 的结论。在现代前端开发(Vue, React, Node.js)中,请遵循以下原则:
- 彻底抛弃 var:它属于过去,变量提升和无块级作用域是 Bug 的温床。
- 默认使用 const:在 90% 的场景下,我们声明的变量(函数、组件、配置项、对象引用)都不需要重新赋值。用 const 可以语义化地告诉阅读代码的人:“这个变量不会改变”。
- 需要修改时用 let:只有在基本数据类型确实需要变化(如计数器 count++、循环变量 i)时,才使用 let。
记住这个优先级:
⭐⭐⭐⭐⭐ const
⭐⭐⭐ let
🚫 var
作者:NEXT06
你在项目中还在用 var 吗?欢迎在评论区留言讨论!觉得有用的话,点个赞支持一下吧 👍