掌握数据类型,写出更健壮可靠的代码
如果你是初学者,可能会对 null
和 undefined
的区别感到困惑;如果你已经有经验,或许曾在类型判断和引用赋值上踩过坑。不用担心,这篇文章将带你从入门到精通,彻底搞懂 JavaScript 数据类型的所有细节。
一、JavaScript 的两大数据类型家族
JavaScript 的数据类型主要分为两大类:基本数据类型 (原始类型)和引用数据类型(对象类型)。
截至 ES2023,JavaScript 共有 8 种数据类型。
基本数据类型包括:
- Undefined :表示变量已声明但未赋值,只有一个值
undefined
- Null :表示空值或不存在的对象,只有一个值
null
- Boolean :表示逻辑值,只有
true
和false
两个值 - Number :表示整数或浮点数,还包括特殊值
NaN
(Not a Number)和Infinity
- String:表示文本数据,可以用单引号、双引号或反引号包裹
- Symbol:ES6 新增,表示唯一的、不可变的值
- BigInt:ES2020 新增,表示任意精度的整数
引用数据类型 主要是 Object,还包括其派生类型:
- Object:键值对的集合
- Array:有序的元素集合
- Function:可执行的对象
- 其他内置对象:如 Date、RegExp、Map、Set 等
二、基本数据类型 vs 引用数据类型:核心区别
为什么要把数据类型分成这两大类呢?因为它们在使用上有根本性的区别。
存储位置不同 基本数据类型存储在栈内存 中,直接存储值本身。引用数据类型存储在堆内存中,变量在栈内存中存储的是指向堆内存的地址引用。
赋值方式不同 基本数据类型赋值是复制值,赋值后两个变量互不影响。
javascript
let a = 10;
let b = a; // 值拷贝
b = 20;
console.log(a); // 10(互不影响)
引用数据类型赋值是复制引用,赋值后两个变量指向同一个对象。
javascript
let obj1 = { name: 'John' };
let obj2 = obj1; // 指针拷贝
obj2.name = 'Alice';
console.log(obj1.name); // 'Alice'(指向同一对象)
比较方式不同 基本数据类型比较的是值是否相等。
javascript
5 === 5; // true
引用数据类型比较的是内存地址(是否同一个对象)。
javascript
{} === {}; // false
是否可变 基本数据类型是不可变 的,修改时会创建新值。引用数据类型是可变的,可以直接修改对象属性。
三、typeof:类型判断的常用工具
typeof
操作符是最常用的类型判断工具,它可以返回一个字符串,表示变量的数据类型。
javascript
console.log(typeof "Hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object"(注意:这是一个历史遗留问题)
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof 123n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function() {}); // "function"
需要注意的是,typeof null
返回 "object"
是 JavaScript 早期实现的一个历史遗留问题,而不是因为 null
本身是一个对象。
另外,typeof
对于数组也返回 "object"
,这显然不够精确。对于数组的类型判断,可以使用 Array.isArray()
方法。
javascript
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
四、null vs undefined:看似相似却不同
很多开发者对 null
和 undefined
的区别感到困惑。它们都表示某种"空"的概念,但在语义和使用上有所不同。
undefined 表示变量已声明但未赋值,是 JavaScript 引擎给变量分配的默认值。
javascript
let x;
console.log(x); // 输出: undefined
null 表示"空"或"无值",是一个显式赋值的空对象指针,通常用于手动释放对象引用。
javascript
let user = null; // 主动设置为空
在比较时,它们也表现出有趣的行为:
javascript
console.log(null == undefined); // true(抽象相等比较)
console.log(null === undefined); // false(严格相等比较,类型不同)
五、特殊数据类型:Symbol 和 BigInt
Symbol 是 ES6 引入的一种新的原始数据类型,表示唯一的值。即使使用相同的描述创建两个 Symbol,它们也是不相等的。
javascript
let s1 = Symbol('id');
let s2 = Symbol('id');
console.log(s1 === s2); // false
Symbol 常用于对象属性的键,确保属性名的唯一性,避免属性冲突。
javascript
const ID = Symbol('id');
const user = {
[ID]: 123,
name: 'Alice'
};
BigInt 是 ES2020 引入的一种新的原始数据类型,用于表示任意精度的整数。JavaScript 的 Number 类型无法安全地表示大于 2^53 的整数,BigInt 解决了这个问题。
javascript
const bigNum = 9007199254740991n; // 注意后面的 n
const alsoBig = BigInt("9007199254740991");
BigInt 不能直接与 Number 类型进行混合运算,需要先进行转换。
六、常见陷阱与最佳实践
陷阱一:意外修改共享对象由于引用类型的赋值是复制引用而不是值,可能会意外修改共享对象。
javascript
let obj1 = { name: 'John' };
let obj2 = obj1; // 复制引用,而不是对象本身
obj2.name = 'Alice'; // 修改了 obj1 和 obj2 共享的对象
console.log(obj1.name); // 'Alice'
解决方案:需要时使用深拷贝创建新对象。
陷阱二:NaN 的特殊行为 NaN
(Not a Number)是一个特殊的数字值,表示不是一个数字。它有一个独特的行为:它是 JavaScript 中唯一不等于自身的值。
javascript
console.log(NaN === NaN); // false
解决方案:使用 isNaN()
函数或 Number.isNaN()
方法检查 NaN。
javascript
console.log(isNaN(NaN)); // true
console.log(isNaN("abc")); // true(无法转换为数字)
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("abc")); // false(Number.isNaN 更严格)
最佳实践一:使用全等比较 尽量使用 ===
而不是 ==
进行比较,避免隐式类型转换带来的意外结果。
最佳实践二:明确的类型检查使用组合方式进行类型检查,提高代码的健壮性。
javascript
// 检查数组
function isArray(obj) {
return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === "[object Array]";
}
// 检查 null
function isNull(obj) {
return obj === null;
}
// 检查 undefined
function isUndefined(obj) {
return obj === void 0; // 或者 typeof obj === "undefined"
}
七、总结
JavaScript 的数据类型看似简单,但深入理解它们对于编写健壮、可靠的代码至关重要。基本数据类型和引用数据类型在存储、赋值和比较上的根本差异,是许多常见编程错误的根源。
通过掌握 typeof
、instanceof
和 Object.prototype.toString.call()
等类型判断方法,以及理解 null
和 undefined
的区别,你可以避免许多常见的陷阱。
希望这篇文章帮助你彻底理解了 JavaScript 的数据类型。如果有任何问题或想法,欢迎在评论区留言讨论!