前言
JavaScript 是一门灵活且强大的编程语言,但其早期设计的一些特性如 var 变量声明方式存在一些问题。随着 ES6 的引入,let 和 const 提供了更加合理和安全的方式来声明变量。本文将带你深入探讨这些关键字的区别及其最佳实践。
一、ES6之前的常量与变量声明
1.声明变量
ES6 之前的变量声明都采用var关键字
var height=180
var age=18
但 var 关键字在如今的ES6时期几乎不再见人使用,这是因为其本身存在一定弊端
我们都知道 JS 是一种弱类型语言,其变量的类型是由值来决定的,例如
var a=12 //a为数值类型
var name='xiaoming' //字符串类型
1.声明常量
在ES6之前的时期,JS并没有专门的关键字用来声明常量,而是采用一种约定俗成的方法,所有程序员都默认——大写的定义就是常量,例如:
var PI=3.1415
console.log(PI) //3.1415
PI=3.14 //重新赋值
console.log(PI) //3.14
虽然这个值仍然是可以重新赋值改变的,但程序员们都默契的规定这样大写的定义就是常量,常量不应该再对齐进行赋值改变。
这种约定俗成却没有严格限制的方式,给当时的代码编写带来了不少的困扰,随着时间的迭代,在ES6版本,JS终于解决了这方面的问题
二、ES6之后的常量与变量声明
1.声明变量
当2015年ES6推出后,给出了let关键字,至此JS对于变量的声明便可以用let 和var,但在当今JS代码的编写总,我们几乎不再使用var进行声明变量,这是因为其本身存在不少的缺陷,我们后面会重点讨论。
let age=18
let name='xiaoming'
2.常量声明
随着ES6的推出,终于对常量的声明有了专门的关键字:const
const PI=3.1415
console.log(PI) //3.1415
PI =3.14 //重新赋值、
console。log(PI)
根据const的特性,以上代码是无法正常运行的,因为用const定义的常量被重新赋值了,控制台会❌ 报错!TypeError: Assignment to constant variable. (类型错误:给常量变量赋值)
const关键字的使用,也使得现在的JS代码更加规范,不再容易出现对于常量重新赋值的问题
三、var关键字的弊端
我们前面提到,随着ES6的推出,对于变量定义,程序员们大多都在使用let而不再使用早期的var,这是因为var关键字其本身存在不少缺陷
var关键字存在以下特点:
- 函数作用域而非块级作用域
- 变量提升
- 允许重复声明
1.函数作用域(Function Scope),而非块级作用域(Block Scope)
这是 var 最大的问题之一。
函数作用域:var 声明的变量只在函数内部有效,而在 if、for、while 等代码块 {} 中声明的 var 变量,会“泄露”到整个函数或全局作用域。
if (true) {
var x = 10;
}
console.log(x); // 输出 10!变量x在if块外依然可访问
for (var i = 0; i < 3; i++) {
// 循环体
}
console.log(i); // 输出 3!i在循环结束后仍然存在
这与大多数其他编程语言的行为不同,容易导致命名冲突和意外覆盖。
2.变量提升
使用 var 声明的变量会被“提升”到其作用域的顶部。
console.log(a); // 控制台会打印 undefined,而不是报错
var a = 5;
实际上,JavaScript 引擎会将上述代码理解为:
var a; // 声明被提升
console.log(a); // 此时a是undefined
a = 5; // 赋值保留在原位置
这种行为容易让人误以为变量已定义,从而引发难以调试的 undefined 错误,相较于 Java或是C++等大型语言,其容易产生歧义,通俗的说就是这种情况不符合大部分程序员的认知,使得代码可读性降低
3. 允许重复声明
var 允许在同一作用域内重复声明同一个变量,不会报错。
var name = "Alice";
var name = "Bob"; // 不报错,直接覆盖
console.log(name); // "Bob"
这在大型项目中容易导致意外覆盖,降低代码安全性。
四、let和const的优势
随着 ES6(ES2015)的发布,JavaScript 引入了两个更优秀的变量声明方式:let 和 const。它们解决了 var 的几乎所有痛点。
示例对比:
// 使用 var
if (true) {
var y = 20;
}
console.log(y); // 20 —— 意外泄露!
// 使用 let
if (true) {
let z = 30;
}
console.log(z); // ReferenceError: z is not defined —— 更安全!
// 使用 var:奇怪的提升行为
console.log(value); // undefined
var value = "hello";
// 使用 let:更合理的错误提示
console.log(value); // ReferenceError: Cannot access 'value' before initialization
let value = "hello";
JS是一门脚本语言,它的编译阶段和执行阶段不像 Java/C++那样分开的那么清晰,在编译阶段,代码执行的那一刹那,var声明的标量就进行了变量提升,接下来代码就进入到了执行阶段,赋值发生在执行阶段,变量提升不利于代码的可读性,是JS应该废弃的糟粕
而ES6给出了一个暂时性死区的概念,简而言之就是let和const的声明也存在变量提升,但是在编译阶段将其存在这个暂时性死区——>变量存在,但不能使用,访问会出错
为什么叫“暂时性死区”?
- “死区” :表示你不能安全地使用这个变量,访问它会出错。
- “暂时性” :这个“死区”只存在于从进入作用域到变量实际声明语句执行之前这段时间。
- 一旦执行到声明语句(如
let x = 10;),变量就被初始化,暂时性死区结束,之后就可以正常访问。
五、const变量真的不能变吗?
讲了那么多var和let,我们再来讨论一下const声明的常量,在我们一般的认知中,常量就是不能改变的,事实上const声明的简单数据类型也确实如此
const PI=3.1415 //第一次赋值
PI =3.14 //重新赋值
此时运行代码,❌ 报错! TypeError: Assignment to constant variable.
但如果我们用const定义一个复杂数据类型,也不能改变吗?
const obj = {
name: 'xm',
age: 18
}
console.log(obj.age) //18
obj.age = 28
console.log(obj.age) //28
可以看到,在我们认知中const声明的常量在重新赋值的过程中发生了改变,且程序正常运行没有发生报错
你可能就会问,为什么前面声明的简单数据类型再赋值就报错了,而改变对象这个复杂数据类型就正确运行了呢?这其实触及到了JS中非常核心的一个概念:变量绑定 vs. 值的可变性
1.我们先来澄清一个误解:
- ❌ const让值不可改变
- ✅ const只保证变量绑定不可变,即变量名不能重新指向另一个值,但不能保证它指向的值本身不可变
2.const 到底限制了什么
const 限制的是变量的赋值操作(re-assignment),而不是值的内部修改。
const name = "Alice";
name = "Bob"; // ❌ 报错!TypeError: Assignment to constant variable.
const user = { name: "Alice" };
user.name = "Bob"; // ✅ 允许!修改的是对象内部的属性
console.log(user); // { name: "Bob" }
在第二个例子中,我们没有改变 user 这个变量的指向,它仍然指向原来的那个对象。我们只是修改了该对象内部的 name 属性。
3.简单数据类型 vs 复杂数据类型的本质区别
1). 简单数据类型(原始类型 Primitive Types)
包括:string、number、boolean、null、undefined、symbol(符合)、bigint(大整数)
- 它们是按值存储的。
- 当你“修改”一个原始类型的值时,实际上是创建了一个新值,然后尝试让变量指向它。
javascript
编辑
let count = 1;
count = 2; // count 现在指向一个新的数字值 2
对于 const:
javascript
编辑
const count = 1;
count = 2; // ❌ 不允许重新赋值!
2). 复杂数据类型(引用类型 Reference Types)
包括:object、array、function
- 它们是按引用存储的。
- 变量保存的不是实际数据,而是一个“地址”(指针),指向内存中对象的实际位置。
javascript
编辑
const arr = [1, 2, 3];
arr.push(4); // ✅ 允许!我们没有改变 arr 的指向,只是修改了它指向的对象
console.log(arr); // [1, 2, 3, 4]
arr = [5, 6]; // ❌ 报错!不能让 arr 指向一个新数组
基于以上,我们可以简单的理解:const定义的简单数据类型不可重新赋值,而复杂数据类型,可以对其内部数据再修改。
你可能又会有一个疑问:那我有什么办法能让const定义的复杂数据类型内部的值也不可改变吗?
有的有的,兄弟有的
4.如何真正让对象“不可变”
1). Object.freeze()
const user = Object.freeze({ name: "Alice", age: 25 });
user.name = "Bob"; // ❌ 无效!在严格模式下会报错
console.log(user.name); // "Alice"
5.现代开发中的最佳实践
- 优先使用
const:如果你不打算改变变量的值(如对象、数组、配置项),使用const。 - 其次使用
let:如果变量需要重新赋值(如循环计数器、累加器),使用let。 - 避免使用
var:除非维护非常老的代码,否则不要再使用var。
简单理解就是:能用const就不用let,能用let就不用var
总结
var 曾经是 JavaScript 声明变量的唯一方式,但由于其函数作用域、变量提升、允许重复声明等特性,容易导致 bug 和代码混乱。而 let 和 const 提供了更清晰、更安全的变量声明机制,因此在现代 JavaScript 开发中,var 已经基本被淘汰。
本篇文章虽然讨论的是每一个JS开发者初学就涉及的三个关键字,但其底层涉及到许多JS核心知识,是互联网大厂常考面试题,文章内容都是作者学习的个人见解,希望对各位学习有所帮助,如有错误的地方,或者更好的见解,欢迎讨论。