JavaScript判断数据类型的5种方法:typeof、Array.isArray、instanceof与结构守卫

编程狮 2026-09-18 15:59:39 浏览数 (13)
反馈

JavaScript 判断数据类型没有万能 API。正确做法是按场景分层:

  • 基本类型先用 typeof
  • 数组用 Array.isArray
  • 类实例用 instanceof
  • DateRegExpMap 等内建对象可用 Object.prototype.toString.call 辅助判断;
  • 来自网络、存储或用户输入的业务对象,必须做结构守卫。

判断数据类型5种方法一次讲清

本文在 Node.js 24.19.0 实测 null、NaN、数组、Date、函数和普通对象,比较五种 JavaScript 类型判断方式。你会看到 typeof null、跨窗口数组、伪造原型和“看起来是对象但字段不合法”等边界,并能按数据来源选择正确的检查层级。

一、先看结论:按数据来源选择方法

你的问题 优先方法 注意点
是不是基本类型? typeof null 要单独判断,NaN 要用 Number.isFinite
是不是数组? Array.isArray 跨 iframe/window 也可靠
是不是某个类实例? instanceof 同一 realm 可靠,跨窗口慎用
是不是 Date、RegExp、Map? Object.prototype.toString.call 只能辅助,标签可被 Symbol.toStringTag 影响
能不能进入业务? 结构守卫 / schema 检查字段、类型、范围、枚举和长度

一句话:语言类型用内建 API,业务类型必须验证契约。

二、typeof:基本类型首选,但要处理两个例外

const values = [undefined, true, 42, 42n, '编程狮', Symbol('id'), () => {}];

for (const value of values) {
  // 逐个输出 typeof 结果
  console.log(typeof value);
}

输出依次是:

typeof 结果
undefined 'undefined'
true 'boolean'
42 'number'
42n 'bigint'
'编程狮' 'string'
Symbol('id') 'symbol'
() => {} 'function'

判断未声明变量时,typeof missingName 会返回 'undefined',不会抛 ReferenceError

// 未声明变量不会抛 ReferenceError
console.log(typeof missingName); // 'undefined'

但要注意:如果变量是 letconst,并且处于暂时性死区,typeof 仍会抛错。

// console.log(typeof name); // ReferenceError
// let name = 'JavaScript';

typeof 有两个著名例外:

  • typeof null 返回 'object',这是历史兼容行为;
  • typeof NaN 返回 'number',因为 NaN 属于 number 类型,只是表示“不是有效数值”。

判断有限数字时,应组合 typeofNumber.isFinite

function isFiniteNumber(value) {
  // 先确认是 number 类型,再排除 NaN、Infinity、-Infinity
  return typeof value === 'number' && Number.isFinite(value);
}

console.log(isFiniteNumber(42));       // true
console.log(isFiniteNumber(NaN));      // false
console.log(isFiniteNumber(Infinity)); // false
console.log(isFiniteNumber('42'));     // false

想复习基本类型、运算符和函数,可参考 JavaScript基础教程

三、Array.isArray:数组判断首选

console.log(Array.isArray([]));                    // true
console.log(Array.isArray({ 0: 'a', length: 1 })); // false,类数组对象不是真数组
console.log(Array.isArray(new Uint8Array(2)));     // false,TypedArray 不是 Array

数组也是对象,所以 typeof [] 只会得到 'object'

MDN Array.isArray文档 说明,该方法执行数组品牌检查,能识别另一个 realm(例如 iframe)创建的数组。而 instanceof Array 可能因为构造函数身份不同返回 false

类数组对象有数字键和 length,但不是真正数组;TypedArray 也有索引和长度,但 Array.isArray 返回 false。需要数组方法时,先确认输入契约,不要只看它“长得像”。

typeof Array.isArray 说明
[] 'object' true 真数组
{ 0: 'a', length: 1 } 'object' false 类数组对象
new Uint8Array(2) 'object' false TypedArray

四、instanceof:检查原型链,适合类实例

class Course {
  constructor(name) {
    this.name = name;
  }
}

const course = new Course('JavaScript');

console.log(course instanceof Course); // true,course 的原型链上有 Course.prototype
console.log(course instanceof Object); // true,Course 继承自 Object

instanceof 回答的是:右侧构造函数的 prototype 是否在左侧对象的原型链上。它不是读取对象的真实业务类型。

它适合判断 Error、自定义类和同一 realm 中的实例。

但有两个边界:

  1. 跨 iframe/window 时,两个 Array 构造函数不是同一个对象;
  2. 手动修改原型链,也可能让普通对象通过检查。

序列化后的 JSON 对象更不会自动恢复为类实例。因此,API 响应和配置文件不应只靠 instanceof 验证。

需要核对构造函数、原型和运算符细节时,可查 JavaScript参考手册

场景 instanceof 是否可靠 建议
同一页面内的自定义类实例 可靠 可用
跨 iframe/window 数组 可能失败 改用 Array.isArray
JSON 反序列化对象 不可靠 用结构守卫
手动改过原型的对象 可能误判 结合结构校验

五、Object.prototype.toString.call:细分内建对象

// 统一封装,返回类似 [object Date] 的标签
const tag = (value) => Object.prototype.toString.call(value);

console.log(tag(undefined));    // [object Undefined]
console.log(tag(null));         // [object Null]
console.log(tag([]));           // [object Array]
console.log(tag(new Date()));   // [object Date]
console.log(tag(/w3cschool/i)); // [object RegExp]

在 Node.js 24.19.0 环境中,常见结果如下:

Object.prototype.toString.call 结果
null [object Null]
NaN [object Number]
[] [object Array]
new Date() [object Date]
函数 [object Function]

这种写法适合区分常见内建对象,但不要把字符串标签当成绝对安全边界。对象可以通过 Symbol.toStringTag 影响显示结果:

// 伪造 toStringTag,让普通对象看起来像数组
const fakeArray = { [Symbol.toStringTag]: 'Array' };

console.log(tag(fakeArray));           // [object Array]
console.log(Array.isArray(fakeArray)); // false,真实数组检查仍然可靠

业务安全校验仍应检查字段、范围和权限,而不是相信一个标签。

六、结构守卫:业务对象必须验证字段

假设接口要求课程配置必须包含非空 title 和字符串数组 tags

typeof config === 'object' 远远不够,因为 null、数组、Date 和缺字段对象都会落进 object

function isCourseConfig(value) {
  return value !== null
    && typeof value === 'object' // 排除 null 和基本类型
    && !Array.isArray(value)     // 排除数组
    && Object.hasOwn(value, 'title')
    && typeof value.title === 'string'
    && value.title.trim().length > 0
    && Object.hasOwn(value, 'tags')
    && Array.isArray(value.tags)
    && value.tags.every(tag => typeof tag === 'string'); // 每个标签都必须是字符串
}

console.log(isCourseConfig({ title: 'JS 类型判断', tags: ['JavaScript'] })); // true
console.log(isCourseConfig({ title: '', tags: [1] })); // false

Object.hasOwn 是现代写法。兼容旧环境时,可以替换为:

Object.prototype.hasOwnProperty.call(value, 'title')

结构守卫把“语言类型”升级为“满足业务契约”。复杂项目可用 schema 库或 TypeScript 配合运行时验证,但静态类型不会自动验证网络输入,边界处仍要检查真实数据。

结构守卫还要避免三个漏洞:

  1. 数组本身也是 object,所以普通对象判断应排除 Array;
  2. 字段存在不代表值合法,价格可能是 NaN,日期字符串可能无法解析,枚举也可能出现未知项;
  3. 继承属性可能通过 in 检查,只接受 JSON 自有字段时,可配合 Object.hasOwn

function isPrice(value) {
  return typeof value === 'number' // 必须是数字类型
    && Number.isFinite(value)      // 排除 NaN、Infinity
    && value >= 0;                 // 业务要求价格非负
}

console.log(isPrice(0));        // true
console.log(isPrice(NaN));      // false
console.log(isPrice(Infinity)); // false

错误信息也应对应结构层级。与其只返回 false,可以在验证入口返回:

{ ok: false, path: 'tags[0]', reason: 'expected string' }

这样调用方能定位字段,日志也不会只剩“类型错误”四个字。

function validateCourseConfig(value) {
  // 第一层:必须是普通对象
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
    return { ok: false, path: '', reason: 'expected plain object' };
  }

  // 第二层:title 必须是非空字符串
  if (typeof value.title !== 'string' || value.title.trim().length === 0) {
    return { ok: false, path: 'title', reason: 'expected non-empty string' };
  }

  // 第三层:tags 必须是数组
  if (!Array.isArray(value.tags)) {
    return { ok: false, path: 'tags', reason: 'expected array' };
  }

  // 第四层:tags 每一项都必须是字符串
  for (let i = 0; i < value.tags.length; i++) {
    if (typeof value.tags[i] !== 'string') {
      return { ok: false, path: `tags[${i}]`, reason: 'expected string' };
    }
  }

  return { ok: true };
}

七、五种方法如何选:推荐决策顺序

顺序 判断目标 推荐方法
1 基本类型 typeof,单独处理 nullNaN
2 是否数组 Array.isArray
3 是否某个类实例 instanceof
4 是否 Date、RegExp、Map 等 Object.prototype.toString.call
5 能否进入业务 结构守卫 / schema

先问是否检查基本类型;是就用 typeof,并单独处理 nullNaN

再问是否必须是数组;用 Array.isArray

再问是否要求某个类实例;在同一 realm 中用 instanceof

需要区分 DateRegExpMap 等内建对象时,用 Object.prototype.toString.call 辅助。

最终只要数据来自网络、存储或用户输入,就回到结构守卫。

完整实测输出可简化为:

null -> typeof object, array false, [object Null]
NaN  -> typeof number, array false, [object Number]
[]   -> typeof object, array true,  [object Array]
Date -> typeof object, array false, [object Date]

JavaScript 数据类型误区通常来自问错问题:你想知道语言层类型,还是想证明数据可用于某个业务操作?前者用内建运算符,后者必须验证契约。

八、工程建议:把验证放在系统边界

性能上不用过早统一成一个万能工具。typeofArray.isArray 足够快且语义清楚,结构守卫的成本主要取决于递归深度和数组长度。

对于大型外部 JSON,应设置最大深度、最大条目数和字符串长度,避免“类型正确但体积失控”拖慢服务。验证通过后再进入业务层,后续函数就不必重复猜测输入。

设计公共函数时,还应区分“拒绝无效输入”和“自动转换输入”。例如字符串 "42" 可以转成数字,但空字符串也会被 Number('') 转为 0。

若接口允许转换,就要先定义空白、十六进制、科学计数法和超范围数怎样处理;若接口只接受数字,结构守卫应直接拒绝字符串。隐式转换会让类型判断结果和最终业务值脱节。

测试用例不能只放正常对象。至少加入 null、数组、缺字段、额外字段、NaN、Infinity、错误枚举和超长字符串。对于跨窗口代码,再加入另一个 realm 创建的数组。每个失败断言都对应一条输入边界,后续维护者才知道检查不是多余代码。

测试输入 目的
null 防止空值进入对象逻辑
[] 防止数组冒充普通对象
缺字段对象 验证必填字段
额外字段对象 决定是否允许扩展字段
NaNInfinity 验证数字范围
错误枚举 验证业务取值
超长字符串 防止体积失控
另一个 realm 的数组 验证跨窗口数组判断

JavaScript 判断数据类型的五种方法

总结

五种方法各有边界:

  • typeof 识别基本类型;
  • Array.isArray 判断数组;
  • instanceof 检查原型链;
  • Object.prototype.toString.call 区分常见内建对象;
  • 结构守卫验证业务数据。

不要写一个巨大的 getType() 字符串函数,然后把所有判断都交给它。把检查放在数据进入系统的边界,并让错误信息说明缺了哪个字段。这样既能避开 typeof null 等 JavaScript 数据类型误区,也能让后续代码获得可靠输入。

延伸学习

  1. 想系统补齐语法,可跟着 JavaScript零基础入门到进阶 学习;
  2. 日常查询 API 时,使用 JavaScript速查手册
  3. 判断数组后还要检查内容时,可继续读 JavaScript数组判断笔记,区分“是数组”和“数组为空”。

常见问题

Q:typeof null 为什么是 object?

A:这是 JavaScript 的历史兼容行为。判断 null 应直接用 value === null,不要把 typeof value === 'object' 当成非空对象证明。

Q:判断数组用 instanceof 可以吗?

A:同一 realm 中通常可以,但跨 iframe/window 可能失败。通用代码优先用 Array.isArray

Q:typeof NaN 为什么是 number?

A:NaN 属于 number 类型,只表示计算结果不是有效数字。需要有效有限数时,使用 typeofNumber.isFinite

Q:TypeScript 有类型后还需要运行时判断吗?

A:需要。TypeScript 类型在编译后会被擦除,网络响应、JSON 和用户输入仍可能不符合声明,系统边界必须做运行时验证。

0 人点赞