变量与数据类型:var、let、const 的区别

说实话,变量声明这块,是每个 JavaScript 初学者都会遇到的坎。我记得我刚入行那会儿,满脑子都是「不就声明个变量吗,有啥好讲的?」结果呢?项目一复杂,各种变量覆盖、作用域混乱,调试到怀疑人生。

今天咱们就把 var、let、const 彻底聊透。顺便把 JavaScript 的几种基本数据类型也捋一遍。

var:老派但坑多

var 是 ES5 时代的产物。它的特点是:函数作用域,不是块级作用域。

什么意思呢?看个例子:

if (true) {
  var name = '张三';
}
console.log(name); // 输出 '张三'

你可能会想:「咦?name 不是在 if 块里声明的吗?怎么外面还能访问?」

对,这就是 var 的坑。它没有块级作用域的概念。只有函数才能限制它的作用范围。

我曾经在项目中遇到过这样一个 bug:一个 for 循环里用 var 声明了循环变量 i,结果循环结束后 i 的值被全局污染了,导致后面的事件回调全用的同一个 i 值。排查了整整一下午。

另外,var 还有变量提升的特性。说白了,就是声明会被提到作用域顶部,但赋值不会。

console.log(a); // 输出 undefined,不会报错
var a = 10;

嗯,这其实挺反直觉的。我个人习惯是:能不用 var 就不用。除非你在维护老项目。

let:现代 JavaScript 的标配

let 是 ES6 引入的。它解决了 var 的两个核心问题:块级作用域和变量提升的怪异行为。

if (true) {
  let age = 25;
  console.log(age); // 25
}
console.log(age); // 报错:age is not defined

看到了吧?let 只在花括号内有效。这才是大多数语言该有的样子。

let 也有变量提升,但和 var 不同——它存在「暂时性死区」。也就是说,在声明之前访问变量,会直接报错,而不是 undefined。

console.log(b); // 报错:Cannot access 'b' before initialization
let b = 20;

我个人觉得,let 的设计更符合直觉。你想想看,变量都没声明完,怎么能用呢?

建议:日常开发中,能用 let 就用 let。它基本覆盖了 var 的所有使用场景,而且更安全。

const:声明常量,但没那么「常」

const 也是 ES6 来的。它用来声明常量——一旦赋值,就不能再重新赋值。

const PI = 3.14159;
PI = 3.14; // 报错:Assignment to constant variable

但这里有个容易踩的坑:const 保证的是引用不变,而不是值不变

什么意思呢?看代码:

const person = { name: '李四' };
person.name = '王五'; // 没问题,可以修改属性
person = {}; // 报错:不能重新赋值

说白了,const 锁的是「变量指向的内存地址」,而不是内存地址里的内容。对于对象和数组来说,内容是可以变的。

我曾经见过一个同事,用 const 声明了一个数组,然后 push 了几个元素,结果一脸懵:「不是说常量不能改吗?」嗯,const 不背这个锅。

我个人习惯是:默认用 const,只有确定需要重新赋值时才用 let。这样代码意图更清晰,也减少意外修改。

三者的对比

特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 有(初始化为 undefined) 有(暂时性死区) 有(暂时性死区)
可重新赋值 可以 可以 不可以
可重复声明 可以 不可以 不可以
使用建议 尽量不用 需要改值时用 默认首选

基本数据类型

JavaScript 有 7 种基本数据类型(也叫原始类型)。我一个个说。

Number:数字

JavaScript 里的数字不分整数和浮点数,统一用 Number 类型。底层是 IEEE 754 双精度浮点数。

let intNum = 42;
let floatNum = 3.14;
let negative = -10;
let notANumber = NaN; // 非数字
let infinity = Infinity;

这里有个经典问题:0.1 + 0.2 === 0.3 的结果是什么?

答案是 false。因为浮点数精度问题。我在项目中做金额计算时,从来不用浮点数,而是用整数(分)或者专门的库。

避坑:涉及金钱计算,别用浮点数。用整数单位(如分)或者 decimal 库。

String:字符串

字符串可以用单引号、双引号或者反引号(模板字符串)。

let str1 = '单引号';
let str2 = "双引号";
let str3 = `模板字符串,可以嵌入 ${str1}`;

我个人最喜欢模板字符串。为什么?因为拼接字符串太方便了。以前用加号拼接,又丑又容易错。

// 以前
let msg = '你好,' + name + ',你今年' + age + '岁了。';

// 现在
let msg = `你好,${name},你今年${age}岁了。`;

你想想看,哪个更清爽?

Boolean:布尔值

就两个值:true 和 false。但 JavaScript 里有个「真值」和「假值」的概念。

以下值会被当作 false:

  • false
  • 0
  • ''(空字符串)
  • null
  • undefined
  • NaN

其他所有值都是 true。包括空数组 [] 和空对象 {}。

注意:空数组 [] 是真值!很多新手会以为它是 false,结果条件判断时踩坑。

Null 和 Undefined

这两个经常被搞混。我简单说:

  • undefined:变量声明了,但没赋值。系统给的默认值。
  • null:主动赋值为空。表示「这里什么都没有」。
let a;
console.log(a); // undefined

let b = null;
console.log(b); // null

我个人习惯是:用 null 表示「空」,用 undefined 表示「未定义」。别混着用。

Symbol:独一无二的值

Symbol 是 ES6 新增的。它用来创建独一无二的标识符。

let sym1 = Symbol('描述');
let sym2 = Symbol('描述');
console.log(sym1 === sym2); // false

即使描述一样,两个 Symbol 也不相等。这在做对象属性时特别有用,可以避免属性名冲突。

我在项目中用过 Symbol 来做私有属性。虽然 JavaScript 没有真正的私有,但 Symbol 属性不会被常规方法遍历到,算是一种约定俗成的做法。

小技巧:Symbol 常用于定义常量,或者作为对象的「隐藏」属性键。

总结一下

变量声明这块,记住一个原则:const 优先,let 次之,var 靠边站

数据类型这块,记住 7 种基本类型:Number、String、Boolean、Null、Undefined、Symbol,还有 ES10 新增的 BigInt(处理超大整数)。

嗯,今天就到这儿。下一章咱们聊运算符和类型转换,到时候再细说那些让人头疼的隐式转换。