JavaScript 数据类型与基础
1. 数据类型详解
JavaScript 共有 8 种数据类型 (ES2020+):
1.1 基本类型 (Primitive Types) - 7种
| 类型 | 说明 | 示例 |
|---|---|---|
Undefined | 声明但未赋值 | let x; // x === undefined |
Null | 空值 (注意 typeof 返回 'object') | let obj = null; |
Boolean | 布尔值 | true, false |
Number | 双精度浮点数 (IEEE 754) | 42, 3.14, NaN, Infinity |
String | 字符串 (不可变) | 'hello', "world" |
Symbol | 唯一标识符 (ES6) | Symbol('id') |
BigInt | 任意精度整数 (ES2020) | 9007199254740993n |
1.2 引用类型 (Reference Type) - 1种
Object(包括 Array, Function, Date, RegExp, Map, Set, WeakMap, WeakSet 等)
2. 存储机制
┌─────────────────────────────────────────────────────────────┐
│ 栈内存 (Stack) 堆内存 (Heap) │
│ ┌────────────────┐ ┌──────────────────────┐ │
│ │ a = 100 │ │ │ │
│ ├────────────────┤ │ { name: 'Alice' } │ │
│ │ b = 'hello' │ │ ▲ │ │
│ ├────────────────┤ │ │ │ │
│ │ obj = 0x1234 ──┼──────────────┼────────┘ │ │
│ └────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘关键区别
| 特性 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈 | 堆 (栈中存指针) |
| 大小 | 固定 | 动态 |
| 赋值操作 | 值拷贝 | 引用拷贝 |
| 比较方式 | 值比较 | 引用地址比较 |
javascript
// 基本类型: 值拷贝
let a = 1;
let b = a;
b = 2;
console.log(a); // 1 (不受影响)
// 引用类型: 引用拷贝
let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 2;
console.log(obj1.x); // 2 (被修改!)3. 类型判断
3.1 方法对比
| 方法 | 适用场景 | 返回值示例 | 缺陷 |
|---|---|---|---|
typeof | 基本类型 | 'number', 'object' | null → 'object' |
instanceof | 引用类型 | true/false | 跨 iframe 失效 |
Object.prototype.toString.call() | 通用 | '[object Array]' | 无 |
Array.isArray() | 数组 | true/false | 只能判断数组 |
constructor | 引用类型 | Constructor 函数 | 可被修改 |
3.2 typeof 陷阱
javascript
typeof undefined // 'undefined'
typeof null // 'object' ⚠️ 历史 Bug!
typeof NaN // 'number' ⚠️ 虽然是 Not-a-Number
typeof function(){} // 'function'
typeof [] // 'object' ⚠️ 数组也是 object
typeof Symbol() // 'symbol'
typeof 123n // 'bigint'3.3 通用类型检测函数
javascript
function getType(value) {
if (value === null) return 'null';
const type = typeof value;
if (type !== 'object') return type;
// [object Type] -> type
return Object.prototype.toString.call(value)
.slice(8, -1).toLowerCase();
}
// 使用示例
getType([]); // 'array'
getType({}); // 'object'
getType(new Date()); // 'date'
getType(/abc/); // 'regexp'
getType(new Map()); // 'map'完整代码: code/type-check.js
4. 类型转换
4.1 显式转换
javascript
// 转 Number
Number('123') // 123
Number('123abc') // NaN
Number(true) // 1
Number(null) // 0
Number(undefined) // NaN
parseInt('123abc') // 123 (解析到非数字为止)
parseFloat('3.14m') // 3.14
// 转 String
String(123) // '123'
String(null) // 'null'
String(undefined) // 'undefined'
(123).toString() // '123'
// 转 Boolean
Boolean(0) // false
Boolean('') // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean([]) // true ⚠️ 空数组是 truthy!
Boolean({}) // true ⚠️ 空对象是 truthy!4.2 隐式转换规则
ToPrimitive 转换机制
当对象需要转换为基本类型时,按以下顺序调用:
[Symbol.toPrimitive](hint)- 如果存在hint === 'number'→valueOf()→toString()hint === 'string'→toString()→valueOf()
javascript
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 42;
if (hint === 'string') return 'hello';
return true; // default
}
};
+obj // 42 (hint: number)
`${obj}` // 'hello' (hint: string)
obj + '' // 'true' (hint: default)4.3 经典面试题
javascript
// 1. [] == ![] 为什么等于 true?
[] == ![]
// ![] → false (数组是 truthy)
// [] == false
// [] → '' (ToPrimitive)
// '' == false
// 0 == 0 → true
// 2. {} + [] 和 [] + {}
{} + [] // 0 ({} 被解析为空代码块)
[] + {} // '[object Object]'
// 3. 比较运算
null == undefined // true (特殊规则)
null === undefined // false
NaN === NaN // false ⚠️
// 4. 实现 a == 1 && a == 2 && a == 3
const a = {
value: 1,
valueOf() {
return this.value++;
}
};
console.log(a == 1 && a == 2 && a == 3); // true4.4 == vs === 速查表
| x | y | == | === |
|---|---|---|---|
null | undefined | ✅ | ❌ |
'1' | 1 | ✅ | ❌ |
true | 1 | ✅ | ❌ |
[] | false | ✅ | ❌ |
NaN | NaN | ❌ | ❌ |
最佳实践: 始终使用
===避免隐式转换带来的困惑
5. 深拷贝 vs 浅拷贝
5.1 浅拷贝方法
javascript
// 1. Object.assign()
const shallow1 = Object.assign({}, original);
// 2. 扩展运算符
const shallow2 = { ...original };
// 3. Array.prototype.slice()
const arr2 = arr1.slice();
// 4. Array.from()
const arr3 = Array.from(arr1);5.2 深拷贝方法对比
| 方法 | 优点 | 缺点 |
|---|---|---|
JSON.parse(JSON.stringify()) | 简单 | 丢失函数/Symbol/循环引用/undefined |
structuredClone() (ES2022) | 原生支持 | 不支持函数/Symbol |
| 手写递归 | 完全可控 | 需处理边界情况 |
lodash _.cloneDeep() | 功能完善 | 需引入库 |
5.3 structuredClone (现代方案)
javascript
const original = {
date: new Date(),
nested: { a: 1 },
arr: [1, 2, 3],
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3])
};
original.circular = original; // 循环引用
const cloned = structuredClone(original);
console.log(cloned.date instanceof Date); // true
console.log(cloned.circular === cloned); // true ✅ 正确处理循环引用5.4 手写深拷贝
javascript
function deepClone(target, map = new WeakMap()) {
// 1. 基本类型和 null 直接返回
if (target === null || typeof target !== 'object') {
return target;
}
// 2. 处理日期和正则
if (target instanceof Date) return new Date(target);
if (target instanceof RegExp) return new RegExp(target);
// 3. 处理循环引用
if (map.has(target)) {
return map.get(target);
}
// 4. 获取构造函数,保证原型链继承
const Ctor = target.constructor;
const cloneTarget = new Ctor();
map.set(target, cloneTarget);
// 5. 处理 Map
if (target instanceof Map) {
target.forEach((value, key) => {
cloneTarget.set(deepClone(key, map), deepClone(value, map));
});
return cloneTarget;
}
// 6. 处理 Set
if (target instanceof Set) {
target.forEach(value => {
cloneTarget.add(deepClone(value, map));
});
return cloneTarget;
}
// 7. 处理对象/数组
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key], map);
}
}
return cloneTarget;
}完整代码: code/deep-clone.js
6. Number 精度问题
6.1 IEEE 754 双精度浮点数
javascript
0.1 + 0.2 === 0.3 // false ⚠️
0.1 + 0.2 // 0.30000000000000004
// 解决方案
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true6.2 安全整数范围
javascript
Number.MAX_SAFE_INTEGER // 9007199254740991 (2^53 - 1)
Number.MIN_SAFE_INTEGER // -9007199254740991
9007199254740992 === 9007199254740993 // true ⚠️ 精度丢失
// 使用 BigInt 处理大整数
const big = 9007199254740993n;
big + 1n // 9007199254740994n ✅7. Symbol 与 BigInt
7.1 Symbol
javascript
// 创建唯一标识
const id1 = Symbol('id');
const id2 = Symbol('id');
id1 === id2 // false
// 作为对象属性键
const obj = {
[id1]: 'value1',
name: 'test'
};
Object.keys(obj) // ['name'] - Symbol 不可枚举
Object.getOwnPropertySymbols(obj) // [Symbol(id)]
// 全局 Symbol
const globalSym = Symbol.for('shared');
Symbol.keyFor(globalSym) // 'shared'
// 内置 Symbol
Symbol.iterator // 迭代器
Symbol.toStringTag // 自定义 toString 标签
Symbol.toPrimitive // 类型转换7.2 BigInt
javascript
// 创建 BigInt
const big1 = 123n;
const big2 = BigInt(123);
// 运算 (不能与 Number 混合运算)
10n + 20n // 30n
10n / 3n // 3n (向零取整)
// 10n + 10 // ❌ TypeError
// 比较可以混合类型
10n === 10 // false (类型不同)
10n == 10 // true
10n > 9 // true8. 面试高频问题
Q1: 如何准确判断数组?
javascript
Array.isArray([]) // ✅ 推荐
Object.prototype.toString.call([]) === '[object Array]' // ✅ 通用
[] instanceof Array // ⚠️ 跨 iframe 失效Q2: null 和 undefined 的区别?
undefined: 变量声明但未赋值null: 主动赋予的空值typeof null === 'object'是历史 Bug
Q3: 为什么 0.1 + 0.2 !== 0.3?
IEEE 754 双精度浮点数无法精确表示十进制小数,使用 Number.EPSILON 比较。
Q4: Object.is() vs ===
javascript
Object.is(NaN, NaN) // true ✅
NaN === NaN // false
Object.is(+0, -0) // false
+0 === -0 // trueQ5: 如何判断一个变量是否为 NaN?
javascript
Number.isNaN(NaN) // true ✅ 推荐
isNaN(NaN) // true
isNaN('hello') // true ⚠️ 全局 isNaN 会先转 Number
Number.isNaN('hello') // false ✅