Skip to content

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 转换机制

当对象需要转换为基本类型时,按以下顺序调用:

  1. [Symbol.toPrimitive](hint) - 如果存在
  2. hint === 'number'valueOf()toString()
  3. 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); // true

4.4 == vs === 速查表

xy=====
nullundefined
'1'1
true1
[]false
NaNNaN

最佳实践: 始终使用 === 避免隐式转换带来的困惑


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  // true

6.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           // true

8. 面试高频问题

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             // true

Q5: 如何判断一个变量是否为 NaN?

javascript
Number.isNaN(NaN)        // true ✅ 推荐
isNaN(NaN)               // true
isNaN('hello')           // true ⚠️ 全局 isNaN 会先转 Number
Number.isNaN('hello')    // false ✅

前端面试知识库