JavaScript 判断数据类型没有万能 API。正确做法是按场景分层:
- 基本类型先用
typeof; - 数组用
Array.isArray; - 类实例用
instanceof; Date、RegExp、Map等内建对象可用Object.prototype.toString.call辅助判断;- 来自网络、存储或用户输入的业务对象,必须做结构守卫。

本文在 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'
但要注意:如果变量是 let 或 const,并且处于暂时性死区,typeof 仍会抛错。
// console.log(typeof name); // ReferenceError
// let name = 'JavaScript';
typeof 有两个著名例外:
typeof null返回'object',这是历史兼容行为;typeof NaN返回'number',因为 NaN 属于 number 类型,只是表示“不是有效数值”。
判断有限数字时,应组合 typeof 和 Number.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 中的实例。
但有两个边界:
- 跨 iframe/window 时,两个
Array构造函数不是同一个对象; - 手动修改原型链,也可能让普通对象通过检查。
序列化后的 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 配合运行时验证,但静态类型不会自动验证网络输入,边界处仍要检查真实数据。
结构守卫还要避免三个漏洞:
- 数组本身也是 object,所以普通对象判断应排除 Array;
- 字段存在不代表值合法,价格可能是 NaN,日期字符串可能无法解析,枚举也可能出现未知项;
- 继承属性可能通过
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,单独处理 null、NaN |
| 2 | 是否数组 | Array.isArray |
| 3 | 是否某个类实例 | instanceof |
| 4 | 是否 Date、RegExp、Map 等 | Object.prototype.toString.call |
| 5 | 能否进入业务 | 结构守卫 / schema |
先问是否检查基本类型;是就用 typeof,并单独处理 null 和 NaN。
再问是否必须是数组;用 Array.isArray。
再问是否要求某个类实例;在同一 realm 中用 instanceof。
需要区分 Date、RegExp、Map 等内建对象时,用 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 数据类型误区通常来自问错问题:你想知道语言层类型,还是想证明数据可用于某个业务操作?前者用内建运算符,后者必须验证契约。
八、工程建议:把验证放在系统边界
性能上不用过早统一成一个万能工具。typeof 和 Array.isArray 足够快且语义清楚,结构守卫的成本主要取决于递归深度和数组长度。
对于大型外部 JSON,应设置最大深度、最大条目数和字符串长度,避免“类型正确但体积失控”拖慢服务。验证通过后再进入业务层,后续函数就不必重复猜测输入。
设计公共函数时,还应区分“拒绝无效输入”和“自动转换输入”。例如字符串 "42" 可以转成数字,但空字符串也会被 Number('') 转为 0。
若接口允许转换,就要先定义空白、十六进制、科学计数法和超范围数怎样处理;若接口只接受数字,结构守卫应直接拒绝字符串。隐式转换会让类型判断结果和最终业务值脱节。
测试用例不能只放正常对象。至少加入 null、数组、缺字段、额外字段、NaN、Infinity、错误枚举和超长字符串。对于跨窗口代码,再加入另一个 realm 创建的数组。每个失败断言都对应一条输入边界,后续维护者才知道检查不是多余代码。
| 测试输入 | 目的 |
|---|---|
null |
防止空值进入对象逻辑 |
[] |
防止数组冒充普通对象 |
| 缺字段对象 | 验证必填字段 |
| 额外字段对象 | 决定是否允许扩展字段 |
NaN、Infinity |
验证数字范围 |
| 错误枚举 | 验证业务取值 |
| 超长字符串 | 防止体积失控 |
| 另一个 realm 的数组 | 验证跨窗口数组判断 |

总结
五种方法各有边界:
typeof识别基本类型;Array.isArray判断数组;instanceof检查原型链;Object.prototype.toString.call区分常见内建对象;- 结构守卫验证业务数据。
不要写一个巨大的 getType() 字符串函数,然后把所有判断都交给它。把检查放在数据进入系统的边界,并让错误信息说明缺了哪个字段。这样既能避开 typeof null 等 JavaScript 数据类型误区,也能让后续代码获得可靠输入。
延伸学习
- 想系统补齐语法,可跟着 JavaScript零基础入门到进阶 学习;
- 日常查询 API 时,使用 JavaScript速查手册;
- 判断数组后还要检查内容时,可继续读 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 类型,只表示计算结果不是有效数字。需要有效有限数时,使用 typeof 加 Number.isFinite。
Q:TypeScript 有类型后还需要运行时判断吗?
A:需要。TypeScript 类型在编译后会被擦除,网络响应、JSON 和用户输入仍可能不符合声明,系统边界必须做运行时验证。

免费 AI IDE



