JavaScript 数据类型与类型检测 完整指南

79 阅读4分钟

一、JavaScript 数据类型

1.1 原始类型 (Primitive Types)

  • Undefinedundefined
  • Nullnull
  • Booleantruefalse
  • Number423.14NaNInfinity
  • String"hello"'world'
  • SymbolSymbol()Symbol('desc') (ES6)
  • BigInt9007199254740991n (ES2020)

1.2 引用类型 (Reference Types)

  • Object{}new Object()
  • Array[]new Array()
  • Functionfunction() {}() => {}
  • Datenew Date()
  • RegExp/regex/new RegExp()
  • Mapnew Map() (ES6)
  • Setnew Set() (ES6)
  • WeakMapnew WeakMap() (ES6)
  • WeakSetnew WeakSet() (ES6)
  • Promisenew Promise() (ES6)
  • 其他内置对象ErrorMathJSON 等

二、类型检测方法

2.1 typeof 操作符

// 基本类型检测
typeof undefined           // "undefined"
typeof true               // "boolean"  
typeof 42                 // "number"
typeof "hello"            // "string"
typeof Symbol()           // "symbol"
typeof 9007199254740991n  // "bigint"

// 函数检测
typeof function() {}      // "function"
typeof class MyClass {}   // "function"

// 局限性
typeof null              // "object" (历史遗留bug)
typeof []                // "object"
typeof {}                // "object"
typeof new Date()        // "object"
typeof /regex/           // "object"

2.2 instanceof 操作符

// 检测构造函数
[] instanceof Array           // true
{} instanceof Object          // true
new Date() instanceof Date    // true

// 原型链检测
[] instanceof Object          // true (所有对象都是Object的实例)
function() {} instanceof Function  // true

// 局限性
'hello' instanceof String     // false (原始类型)
42 instanceof Number          // false (原始类型)

2.3 Object.prototype.toString.call()

// 最全面的类型检测方法
Object.prototype.toString.call(undefined)     // "[object Undefined]"
Object.prototype.toString.call(null)          // "[object Null]"
Object.prototype.toString.call(true)          // "[object Boolean]"
Object.prototype.toString.call(42)            // "[object Number]"
Object.prototype.toString.call("hello")       // "[object String]"
Object.prototype.toString.call(Symbol())      // "[object Symbol]"
Object.prototype.toString.call(9007199254740991n) // "[object BigInt]"

// 对象类型检测
Object.prototype.toString.call({})            // "[object Object]"
Object.prototype.toString.call([])            // "[object Array]"
Object.prototype.toString.call(function() {}) // "[object Function]"
Object.prototype.toString.call(new Date())    // "[object Date]"
Object.prototype.toString.call(/regex/)       // "[object RegExp]"
Object.prototype.toString.call(new Map())     // "[object Map]"
Object.prototype.toString.call(new Set())     // "[object Set]"
Object.prototype.toString.call(new Promise(() => {})) // "[object Promise]"

2.4 专用检测方法

// 数组检测
Array.isArray([])           // true
Array.isArray({})           // false

// NaN 检测
Number.isNaN(NaN)           // true
Number.isNaN("hello")       // false (不会强制类型转换)
isNaN("hello")              // true (会强制类型转换)

// 有限数字检测
Number.isFinite(42)         // true
Number.isFinite(Infinity)   // false
Number.isFinite("42")       // false

// 整数检测
Number.isInteger(42)        // true
Number.isInteger(42.0)      // true
Number.isInteger(42.1)      // false

// BigInt 检测
typeof 42n === 'bigint'     // true

// Symbol 检测
typeof Symbol() === 'symbol' // true

三、综合类型检测函数

3.1 通用类型检测函数

function getType(value) {
    // 处理 null 和 undefined
    if (value === null) return 'null';
    if (value === undefined) return 'undefined';
    
    const type = typeof value;
    
    // 处理基本类型
    if (type !== 'object' && type !== 'function') {
        return type;
    }
    
    // 使用 Object.prototype.toString 获取详细类型
    const toString = Object.prototype.toString.call(value);
    const match = toString.match(/\[object (\w+)\]/);
    
    if (match) {
        return match[1].toLowerCase();
    }
    
    return 'object';
}

// 测试用例
console.log(getType(null));              // "null"
console.log(getType(undefined));         // "undefined" 
console.log(getType(42));                // "number"
console.log(getType("hello"));           // "string"
console.log(getType(true));              // "boolean"
console.log(getType(Symbol()));          // "symbol"
console.log(getType(42n));               // "bigint"
console.log(getType([]));                // "array"
console.log(getType({}));                // "object"
console.log(getType(function() {}));     // "function"
console.log(getType(new Date()));        // "date"
console.log(getType(/regex/));           // "regexp"
console.log(getType(new Map()));         // "map"
console.log(getType(new Set()));         // "set"

3.2 增强版类型检测(支持自定义类型)

class TypeDetector {
    static getType(value) {
        if (value === null) return 'null';
        if (value === undefined) return 'undefined';
        
        const type = typeof value;
        if (type !== 'object' && type !== 'function') {
            return type;
        }
        
        return Object.prototype.toString.call(value)
            .slice(8, -1)
            .toLowerCase();
    }
    
    static isPrimitive(value) {
        return value === null || 
               typeof value !== 'object' && typeof value !== 'function';
    }
    
    static isObject(value) {
        return value !== null && typeof value === 'object';
    }
    
    static isFunction(value) {
        return typeof value === 'function';
    }
    
    static isArray(value) {
        return Array.isArray(value);
    }
    
    static isPlainObject(value) {
        return Object.prototype.toString.call(value) === '[object Object]' &&
               Object.getPrototypeOf(value) === Object.prototype;
    }
    
    static isEmpty(value) {
        if (value === null || value === undefined) return true;
        if (Array.isArray(value) || typeof value === 'string') return value.length === 0;
        if (this.isPlainObject(value)) return Object.keys(value).length === 0;
        if (value instanceof Map || value instanceof Set) return value.size === 0;
        return false;
    }
}

// 使用示例
TypeDetector.getType([])                    // "array"
TypeDetector.isPrimitive("hello")           // true
TypeDetector.isObject({})                   // true  
TypeDetector.isPlainObject({})              // true
TypeDetector.isPlainObject(new Date())      // false
TypeDetector.isEmpty([])                    // true
TypeDetector.isEmpty({})                    // true

四、特殊值检测

4.1 Null 和 Undefined 检测

// 严格相等(推荐)
value === null
value === undefined

// 宽松相等
value == null  // 同时检测 null 和 undefined

// typeof 检测
typeof value === 'undefined'

4.2 NaN 检测

// 可靠的方法
Number.isNaN(value)

// 利用 NaN 的特性(自身不相等)
value !== value

// 不推荐的方法(会进行类型转换)
isNaN(value)  

4.3 假值检测

// JavaScript 的假值
false, 0, "", null, undefined, NaN

// 检测假值
!!value === false
Boolean(value) === false

// 安全检测(排除 null 和 undefined)
value ? true : false

五、ES6+ 新增类型检测

5.1 Map 和 Set 检测

// Map 检测
value instanceof Map
Object.prototype.toString.call(value) === '[object Map]'

// Set 检测  
value instanceof Set
Object.prototype.toString.call(value) === '[object Set]'

// WeakMap 和 WeakSet 检测
value instanceof WeakMap
value instanceof WeakSet

5.2 Promise 检测

// Promise 检测
value instanceof Promise
Object.prototype.toString.call(value) === '[object Promise]'
typeof value.then === 'function'  // thenable 检测

5.3 迭代器检测

// 可迭代对象检测
typeof value[Symbol.iterator] === 'function'

// 生成器函数检测
Object.prototype.toString.call(value) === '[object GeneratorFunction]'

六、面试重点与常见问题

6.1 面试常见问题

  1. typeof null 为什么返回 "object"?

    • 历史遗留问题,JavaScript 初版用类型标签区分值类型
    • null 的机器码是全0,对象类型标签也是0
  2. 如何准确判断数组类型?

    • Array.isArray() (ES5+)
    • Object.prototype.toString.call()
    • 避免使用 instanceof (跨iframe有问题)
  3. instanceof 的原理是什么?

    • 检查构造函数的 prototype 是否在对象的原型链上
  4. 如何判断一个对象是普通对象?

    • Object.getPrototypeOf(obj) === Object.prototype
    • obj.constructor === Object

6.2 最佳实践总结

// 类型检测最佳实践
const typeCheck = {
    // 基本类型
    isUndefined: val => val === undefined,
    isNull: val => val === null,
    isBoolean: val => typeof val === 'boolean',
    isNumber: val => typeof val === 'number',
    isString: val => typeof val === 'string',
    isSymbol: val => typeof val === 'symbol',
    isBigInt: val => typeof val === 'bigint',
    
    // 引用类型
    isObject: val => val !== null && typeof val === 'object',
    isArray: val => Array.isArray(val),
    isFunction: val => typeof val === 'function',
    isDate: val => val instanceof Date,
    isRegExp: val => val instanceof RegExp,
    isMap: val => val instanceof Map,
    isSet: val => val instanceof Set,
    isPromise: val => val instanceof Promise,
    
    // 特殊值
    isNaN: val => Number.isNaN(val),
    isFinite: val => Number.isFinite(val),
    isInteger: val => Number.isInteger(val),
    
    // 通用方法
    getType: val => {
        if (val === null) return 'null';
        const type = typeof val;
        if (type !== 'object') return type;
        return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
    }
};

七、实际应用场景

7.1 参数验证

function validateConfig(config) {
    if (!typeCheck.isPlainObject(config)) {
        throw new Error('Config must be a plain object');
    }
    
    if (config.timeout && !typeCheck.isNumber(config.timeout)) {
        throw new Error('Timeout must be a number');
    }
    
    if (config.retry && !typeCheck.isBoolean(config.retry)) {
        throw new Error('Retry must be a boolean');
    }
}

7.2 深拷贝实现中的类型检测

function deepClone(value) {
    // 基本类型直接返回
    if (!typeCheck.isObject(value)) {
        return value;
    }
    
    // 处理数组
    if (typeCheck.isArray(value)) {
        return value.map(item => deepClone(item));
    }
    
    // 处理日期
    if (typeCheck.isDate(value)) {
        return new Date(value.getTime());
    }
    
    // 处理正则
    if (typeCheck.isRegExp(value)) {
        return new RegExp(value);
    }
    
    // 处理普通对象
    const cloned = {};
    for (const key in value) {
        if (value.hasOwnProperty(key)) {
            cloned[key] = deepClone(value[key]);
        }
    }
    
    return cloned;
}