大厂前端必考题:简单的“var_let_const”如何成为“BATJTMD”的流量密码

52 阅读9分钟

前言

JavaScript 是一门灵活且强大的编程语言,但其早期设计的一些特性如 var 变量声明方式存在一些问题。随着 ES6 的引入,letconst 提供了更加合理和安全的方式来声明变量。本文将带你深入探讨这些关键字的区别及其最佳实践。

一、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对于变量的声明便可以用letvar,但在当今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 声明的变量只在函数内部有效,而在 ifforwhile 等代码块 {} 中声明的 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 引入了两个更优秀的变量声明方式:letconst。它们解决了 var 的几乎所有痛点。

image.png

示例对比:

// 使用 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给出了一个暂时性死区的概念,简而言之就是letconst的声明也存在变量提升,但是在编译阶段将其存在这个暂时性死区——>变量存在,但不能使用,访问会出错

为什么叫“暂时性死区”?

  • “死区” :表示你不能安全地使用这个变量,访问它会出错。
  • “暂时性” :这个“死区”只存在于从进入作用域到变量实际声明语句执行之前这段时间。
  • 一旦执行到声明语句(如 let x = 10;),变量就被初始化,暂时性死区结束,之后就可以正常访问。

五、const变量真的不能变吗?

讲了那么多varlet,我们再来讨论一下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)

包括:stringnumberbooleannullundefinedsymbol(符合)bigint(大整数)

  • 它们是按值存储的。
  • 当你“修改”一个原始类型的值时,实际上是创建了一个新值,然后尝试让变量指向它。
javascript
编辑
let count = 1;
count = 2; // count 现在指向一个新的数字值 2

对于 const

javascript
编辑
const count = 1;
count = 2; // ❌ 不允许重新赋值!

2). 复杂数据类型(引用类型 Reference Types)

包括:objectarrayfunction

  • 它们是按引用存储的。
  • 变量保存的不是实际数据,而是一个“地址”(指针),指向内存中对象的实际位置。
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.现代开发中的最佳实践

  1. 优先使用 const:如果你不打算改变变量的值(如对象、数组、配置项),使用 const
  2. 其次使用 let:如果变量需要重新赋值(如循环计数器、累加器),使用 let
  3. 避免使用 var:除非维护非常老的代码,否则不要再使用 var

简单理解就是:能用const就不用let,能用let就不用var

总结

var 曾经是 JavaScript 声明变量的唯一方式,但由于其函数作用域、变量提升、允许重复声明等特性,容易导致 bug 和代码混乱。而 letconst 提供了更清晰、更安全的变量声明机制,因此在现代 JavaScript 开发中,var 已经基本被淘汰

本篇文章虽然讨论的是每一个JS开发者初学就涉及的三个关键字,但其底层涉及到许多JS核心知识,是互联网大厂常考面试题,文章内容都是作者学习的个人见解,希望对各位学习有所帮助,如有错误的地方,或者更好的见解,欢迎讨论。