跳到主要内容

数据类型与类型判断

问题

JavaScript 有哪些数据类型?如何准确判断数据类型?类型转换的规则是什么?

答案

JavaScript 有 8 种数据类型:7 种原始类型和 1 种引用类型

数据类型分类

原始类型 vs 引用类型

特性原始类型引用类型
存储栈内存堆内存(栈存地址)
比较值比较引用比较
赋值复制值复制引用
可变性不可变可变
// 原始类型 - 值比较
const a = 'hello';
const b = 'hello';
console.log(a === b); // true

// 引用类型 - 引用比较
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Alice' };
console.log(obj1 === obj2); // false

// 引用类型 - 复制引用
const obj3 = obj1;
console.log(obj1 === obj3); // true
obj3.name = 'Bob';
console.log(obj1.name); // 'Bob'

类型判断方法

typeof

// typeof 返回类型字符串
console.log(typeof 42); // 'number'
console.log(typeof 'hello'); // 'string'
console.log(typeof true); // 'boolean'
console.log(typeof undefined); // 'undefined'
console.log(typeof Symbol()); // 'symbol'
console.log(typeof 10n); // 'bigint'

// ⚠️ typeof 的特殊情况
console.log(typeof null); // 'object' ← 历史 bug
console.log(typeof []); // 'object'
console.log(typeof {}); // 'object'
console.log(typeof function(){}); // 'function'

instanceof

// instanceof 检查原型链
const arr: number[] = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

const date = new Date();
console.log(date instanceof Date); // true
console.log(date instanceof Object); // true

// ⚠️ 局限性
// 1. 不能检测原始类型
console.log('hello' instanceof String); // false

// 2. 跨 iframe 问题
// 不同 iframe 有不同的 Array 构造函数

Object.prototype.toString

// 最准确的类型判断方法
function getType(value: unknown): string {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

console.log(getType(42)); // 'number'
console.log(getType('hello')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType(undefined)); // 'undefined'
console.log(getType(null)); // 'null'
console.log(getType(Symbol())); // 'symbol'
console.log(getType(10n)); // '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'

其他判断方法

// Array.isArray - 判断数组
Array.isArray([1, 2, 3]); // true
Array.isArray('hello'); // false

// Number.isNaN - 判断 NaN
Number.isNaN(NaN); // true
Number.isNaN('NaN'); // false(不会类型转换)
isNaN('NaN'); // true(会类型转换)

// Number.isFinite - 判断有限数字
Number.isFinite(42); // true
Number.isFinite(Infinity); // false

// Object.is - 判断值相等
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false
NaN === NaN; // false
+0 === -0; // true

类型转换

显式转换

// 转数字
Number('123'); // 123
Number('123abc'); // NaN
Number(true); // 1
Number(null); // 0
Number(undefined); // NaN
Number([]); // 0
Number([1]); // 1
Number([1, 2]); // NaN
Number({}); // NaN

parseInt('123px'); // 123
parseFloat('3.14'); // 3.14

// 转字符串
String(123); // '123'
String(true); // 'true'
String(null); // 'null'
String(undefined); // 'undefined'
String([1, 2, 3]); // '1,2,3'
String({}); // '[object Object]'

// 转布尔
Boolean(0); // false
Boolean(''); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Boolean([]); // true ← 空数组是 true
Boolean({}); // true ← 空对象是 true

隐式转换

// 1. 字符串拼接
'1' + 2; // '12'
1 + '2'; // '12'
1 + 2 + '3'; // '33'
'1' + 2 + 3; // '123'

// 2. 算术运算(转数字)
'5' - 2; // 3
'5' * '2'; // 10
'6' / '2'; // 3
+'123'; // 123
-'123'; // -123

// 3. 比较运算
'10' > 9; // true('10' 转数字)
'10' > '9'; // false(字符串逐位比较)

// 4. 逻辑运算
!0; // true
!!0; // false
1 && 'hello'; // 'hello'
0 || 'default'; // 'default'
null ?? 'default'; // 'default'

对象转原始值

// 对象转原始值的规则
const obj = {
valueOf(): number {
console.log('valueOf');
return 42;
},
toString(): string {
console.log('toString');
return 'hello';
},
[Symbol.toPrimitive](hint: string): number | string {
console.log('toPrimitive:', hint);
if (hint === 'number') return 42;
if (hint === 'string') return 'hello';
return 'default';
}
};

// 优先级:Symbol.toPrimitive > valueOf/toString
Number(obj); // toPrimitive: number → 42
String(obj); // toPrimitive: string → 'hello'
obj + ''; // toPrimitive: default → 'default'

// 没有 Symbol.toPrimitive 时
const obj2 = {
valueOf(): number { return 42; },
toString(): string { return 'hello'; }
};

Number(obj2); // valueOf → 42
String(obj2); // toString → 'hello'
obj2 + ''; // valueOf → '42'

== 和 === 的区别

// === 严格相等(不进行类型转换)
1 === 1; // true
1 === '1'; // false
null === undefined; // false

// == 宽松相等(进行类型转换)
1 == '1'; // true
null == undefined; // true
true == 1; // true
false == 0; // true
[] == 0; // true
[] == ''; // true
[] == false; // true

== 的转换规则

// == 转换示例
[] == false;
// 1. [] 转原始值 → ''
// 2. false 转数字 → 0
// 3. '' 转数字 → 0
// 4. 0 == 0 → true

[1] == 1;
// 1. [1] 转原始值 → '1'
// 2. '1' 转数字 → 1
// 3. 1 == 1 → true

特殊值

null 和 undefined

// undefined: 未定义
let a;
console.log(a); // undefined

function foo(x?: number): void {
console.log(x); // 不传参数时是 undefined
}

// null: 空值
const obj: { name: string | null } = { name: null };

// 判断
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(typeof null); // 'object'(历史 bug)
console.log(typeof undefined); // 'undefined'

NaN

// NaN: Not a Number
console.log(0 / 0); // NaN
console.log(parseInt('abc')); // NaN
console.log(Math.sqrt(-1)); // NaN

// NaN 不等于任何值,包括自己
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false

// 判断 NaN
Number.isNaN(NaN); // true
Object.is(NaN, NaN); // true

BigInt

// BigInt: 大整数
const big = 9007199254740993n;
const big2 = BigInt('9007199254740993');

// BigInt 不能与 Number 混合运算
// big + 1; // ❌ TypeError

// 需要显式转换
big + 1n; // 9007199254740994n
big + BigInt(1); // 9007199254740994n
Number(big) + 1; // 会丢失精度

常见面试问题

Q1: typeof null 为什么是 'object'?

答案

这是 JavaScript 的历史 bug。在 JS 最初实现中,值以 32 位存储,前 3 位表示类型标签:

  • 000: 对象
  • 001: 整数
  • 010: 浮点数
  • 100: 字符串
  • 110: 布尔

null 的值是机器码 NULL 指针(全零),前 3 位也是 000,被误判为对象。

Q2: 如何准确判断数组?

答案

const arr: number[] = [1, 2, 3];

// 方法 1: Array.isArray(推荐)
Array.isArray(arr); // true

// 方法 2: Object.prototype.toString
Object.prototype.toString.call(arr) === '[object Array]';

// 方法 3: instanceof(有跨 iframe 问题)
arr instanceof Array;

// 方法 4: constructor(不推荐,可被修改)
arr.constructor === Array;

Q3: [] == ![] 的结果?

答案:结果是 true

分析

[] == ![]
// 1. ![] = false(空数组转布尔是 true,取反是 false)
[] == false
// 2. [] 转原始值 = ''
'' == false
// 3. '' 和 false 都转数字
0 == 0
// 4. 结果为 true

Q4: 0.1 + 0.2 === 0.3 吗?

答案:不等于。

console.log(0.1 + 0.2);  // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

// 原因:IEEE 754 浮点数精度问题

// 解决方案
// 方法 1: 指定精度比较
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON; // true

// 方法 2: 转整数计算
(0.1 * 10 + 0.2 * 10) / 10 === 0.3; // true

// 方法 3: toFixed
(0.1 + 0.2).toFixed(1) === '0.3'; // true

Q5: 如何判断一个值是否为空?

答案

function isEmpty(value: unknown): boolean {
// null 或 undefined
if (value == null) return true;

// 字符串
if (typeof value === 'string') return value === '';

// 数组
if (Array.isArray(value)) return value.length === 0;

// 对象
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}

return false;
}

isEmpty(null); // true
isEmpty(undefined); // true
isEmpty(''); // true
isEmpty([]); // true
isEmpty({}); // true
isEmpty(0); // false
isEmpty('hello'); // false

Q6: 如何判断一个变量是数组?有哪些方法?

答案

有四种常见方法,各有优缺点:

const arr: number[] = [1, 2, 3];

// 方法 1: Array.isArray(推荐)
console.log(Array.isArray(arr)); // true
console.log(Array.isArray('hello')); // false
console.log(Array.isArray({ length: 3 })); // false(类数组不算)

// 方法 2: Object.prototype.toString
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true

// 方法 3: instanceof
console.log(arr instanceof Array); // true

// 方法 4: constructor
console.log(arr.constructor === Array); // true
方法优点缺点
Array.isArray()ES5 标准,跨 iframe 安全,语义清晰IE8 不支持(可 polyfill)
Object.prototype.toString.call()跨 iframe 安全,可判断所有类型写法较长
instanceof语法简洁跨 iframe 失败(不同窗口有不同的 Array 构造函数)
.constructor语法简洁可被修改,null/undefined 报错,跨 iframe 失败
// 跨 iframe 问题演示
// 假设 iframeArray 来自另一个 iframe
// iframeArray instanceof Array → false ❌
// Array.isArray(iframeArray) → true ✅

// constructor 可被篡改
const fakeArr = { constructor: Array };
console.log(fakeArr.constructor === Array); // true ❌ 误判
console.log(Array.isArray(fakeArr)); // false ✅ 正确
最佳实践

优先使用 Array.isArray(),它是 ES5+ 标准方法,语义清晰、跨 iframe 安全。Object.prototype.toString.call() 适合需要判断多种类型的通用函数。

Q7: null 和 undefined 的区别?各自的使用场景?

答案

nullundefined 都表示"没有值",但语义不同

  • undefined:表示变量已声明但未赋值,或者缺少预期的值
  • null:表示有意设置为空值,是一种"空的对象引用"
特性nullundefined
含义主动赋值为空未定义/未赋值
typeof'object'(历史 bug)'undefined'
Number() 转换0NaN
== 比较null == undefinedtrueundefined == nulltrue
=== 比较null === undefinedfalseundefined === nullfalse
JSON 序列化保留为 null属性被忽略
函数默认参数不触发默认值触发默认值
// 1. typeof 差异
console.log(typeof null); // 'object'(历史 bug)
console.log(typeof undefined); // 'undefined'

// 2. Number 转换差异
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN

// 3. JSON 序列化差异
const obj = { a: null, b: undefined, c: 1 };
console.log(JSON.stringify(obj)); // '{"a":null,"c":1}' ← b 被忽略

// 4. 函数默认参数差异
function greet(name: string = 'World'): string {
return `Hello, ${name}`;
}
console.log(greet(undefined)); // 'Hello, World' ← 触发默认值
console.log(greet(null!)); // 'Hello, null' ← 不触发默认值

// 5. 使用场景
// undefined:变量未赋值、函数无返回值、参数未传
let x: number | undefined; // 声明未赋值
function foo(): void {} // 返回 undefined
function bar(a?: string): void {} // 可选参数为 undefined

// null:主动设置为空
const user: { name: string | null } = { name: null }; // 用户名为空
const cache: Map<string, string | null> = new Map();
cache.set('key', null); // 显式缓存空值,区分"无缓存"和"缓存为空"
注意

判断空值时,== null 可以同时检查 nullundefined,这是 == 少数推荐的用法:

if (value == null) {
// value 是 null 或 undefined
}
// 等价于
if (value === null || value === undefined) {
// ...
}

Q8: 如何实现一个精确的类型判断函数?

答案

基于 Object.prototype.toString.call() 封装,可以精确判断所有类型:

// 基础版本:返回小写类型字符串
function getType(value: unknown): string {
// 特殊处理 null 和 undefined(虽然 toString 也能处理,但显式更清晰)
if (value === null) return 'null';
if (value === undefined) return 'undefined';

return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

// 使用示例
console.log(getType(42)); // 'number'
console.log(getType('hello')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType(undefined)); // 'undefined'
console.log(getType(null)); // 'null'
console.log(getType(Symbol())); // 'symbol'
console.log(getType(10n)); // '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'
console.log(getType(new WeakMap())); // 'weakmap'
console.log(getType(new Promise(() => {}))); // 'promise'
// TypeScript 类型守卫版本:利用 is 关键字实现类型收窄
function isString(value: unknown): value is string {
return getType(value) === 'string';
}

function isNumber(value: unknown): value is number {
return getType(value) === 'number' && !Number.isNaN(value);
}

function isArray<T = unknown>(value: unknown): value is T[] {
return Array.isArray(value);
}

function isObject(value: unknown): value is Record<string, unknown> {
return getType(value) === 'object';
}

function isFunction(value: unknown): value is Function {
return typeof value === 'function';
}

function isNullOrUndefined(value: unknown): value is null | undefined {
return value == null;
}

// 使用类型守卫实现类型安全
function processValue(value: unknown): string {
if (isString(value)) {
return value.toUpperCase(); // TS 知道 value 是 string
}
if (isNumber(value)) {
return value.toFixed(2); // TS 知道 value 是 number
}
if (isArray<number>(value)) {
return value.join(', '); // TS 知道 value 是 number[]
}
return String(value);
}
// 完整的类型判断工具类
class TypeChecker {
private static toString = Object.prototype.toString;

static getType(value: unknown): string {
if (value === null) return 'null';
if (value === undefined) return 'undefined';
return this.toString.call(value).slice(8, -1).toLowerCase();
}

static isPrimitive(value: unknown): boolean {
const type = typeof value;
return value === null || (type !== 'object' && type !== 'function');
}

static isReferenceType(value: unknown): boolean {
return !this.isPrimitive(value);
}

static isFalsy(value: unknown): boolean {
// JavaScript 中的 7 个 falsy 值
return !value; // false, 0, -0, 0n, '', null, undefined, NaN
}
}

// 使用
console.log(TypeChecker.getType([])); // 'array'
console.log(TypeChecker.isPrimitive(42)); // true
console.log(TypeChecker.isPrimitive({})); // false
console.log(TypeChecker.isReferenceType([])); // true
补充

为什么 Object.prototype.toString.call() 能准确判断类型?

每个对象内部都有一个 [[Class]] 属性(ES6 后改为 Symbol.toStringTag),Object.prototype.toString 会读取它。自定义类也可以通过 Symbol.toStringTag 来控制:

class MyStream {
get [Symbol.toStringTag](): string {
return 'MyStream';
}
}
console.log(Object.prototype.toString.call(new MyStream())); // '[object MyStream]'

相关链接