var_let_const 问到你能答出来吗???

35 阅读6分钟

【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 依然留在原地。所以代码实际执行顺序变成了:

  1. var age; (默认 undefined)
  2. console.log(age);
  3. 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)中,请遵循以下原则:

  1. 彻底抛弃 var:它属于过去,变量提升和无块级作用域是 Bug 的温床。
  2. 默认使用 const:在 90% 的场景下,我们声明的变量(函数、组件、配置项、对象引用)都不需要重新赋值。用 const 可以语义化地告诉阅读代码的人:“这个变量不会改变”。
  3. 需要修改时用 let:只有在基本数据类型确实需要变化(如计数器 count++、循环变量 i)时,才使用 let。

记住这个优先级:
⭐⭐⭐⭐⭐ const
⭐⭐⭐ let
🚫 var


作者:NEXT06

你在项目中还在用 var 吗?欢迎在评论区留言讨论!觉得有用的话,点个赞支持一下吧 👍