JavaScript数据检测方法的全面指南
作者:绅士玖
前言
在 JavaScript 开发中,准确检测数据类型是保证代码健壮性的基础。JavaScript 提供了多种数据类型检测方法,每种方法都有其适用场景和局限性。本文将全面介绍 JavaScript 中各种数据检测方法,包括它们的实现原理、使用场景、优缺点比较,以及如何手动实现一些核心检测方法如 instanceof。
一、typeof 操作符
1. 基本用法
typeof 是最常用的类型检测操作符,返回一个表示数据类型的字符串。
console.log(typeof 42); // "number"
console.log(typeof 'str'); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function(){});// "function"
2. 特点与局限
- 对于原始类型,除了
null外都能正确返回 - 对于引用类型,除了函数外都返回
"object",函数会返回function - 无法区分数组、普通对象等具体对象类型
typeof null返回"object"是历史遗留 bug,原因是在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而null 表示为全零,所以将它错误的判断为 object 。
3. 适用场景
- 快速检测基本数据类型
- 检查变量是否已定义 (
typeof variable !== 'undefined') - 检测函数类型
二、instanceof 操作符
1. 基本用法
instanceof 用于检测构造函数的 prototype 属性是否出现在对象的原型链上。所以判断对象数据类型用instanceof比较好。
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
function Person() {}
const p = new Person();
console.log(p instanceof Person); // true
var str1 = 'hello world'
str1 instanceof String // false
var str2 = new String('hello world')
str2 instanceof String // true
2. instanceof可以判断简单的数据类型么?
能的,兄弟能的,在ES6新增的Symbol.hasInstance方法中,允许自定义 instanceof 操作符的行为。如果不是很了解这个可以在MDN中看Symbol.hasInstance - JavaScript | MDN,下面这种方法就可以实现:
class PrimitiveNumber {
static [Symbol.hasInstance](x) {
return type x === 'number'
}
}
console.log(123 instanceof PrimitiverNumber) // true
这里面就是将instanceof方法重新定义了一下,里面用了type 来判断,所以可以判断基本数据类型number,当然其他的也可以判断,看自己的定义和选择。比如下面的自定义方法:
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
3. 手撕 instanceof
主要是使用了Object.getPrototypeOf()方法,它能够返回指定对象的原型对象
function myInstanceof(left, right) {
// 基本类型直接返回 false
if (typeof left !== 'object' || left === null) return false;
// Object.getPrototypeOf是Object自带的一个方法,可以拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
// 获取原型对象
const prototype = right.prototype;
// 无限循环
while (true) {
// 如果到顶了还没有找到就返回false
if (proto === null) return false;
// 找到了返回true
if (proto === prototype) return true;
// 根据原型链一直往上找
proto = Object.getPrototypeOf(proto);
}
}
// 测试
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof("123", String)); //false
console.log(myInstanceof(new String("123"), String));//true
4. 特点与局限
- 可以检测自定义对象类型
- 对于基本数据类型无效(ES6 的 Symbol.hasInstance 可以改变这一行为)
- 跨窗口/iframe 检测时会失效,因为构造函数不同
- 原型链可能被修改导致检测结果不准确
5. 适用场景
- 检测自定义对象实例
- 检测特定类型的对象(如 Array、Date 等)
三、Object.prototype.toString
1. 基本用法
调用 Object.prototype.toString() 方法可以返回 [object Type] 格式的字符串。
console.log(Object.prototype.toString.call(42)); // [object Number]
console.log(Object.prototype.toString.call('str')); // [object String]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined));// [object Undefined]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
2. 封装通用类型检测函数
function getType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
console.log(getType([])); // 'array'
console.log(getType(null)); // 'null'
console.log(getType(new Map())); // 'map'
代码解析
a. Object.prototype.toString.call(obj)是整个检测机制的核心部分:
Object.prototype.toString: 这是 Object 对象的原始 toString 方法.call(obj): 使用 call 方法将 toString 的 this 指向要检测的对象
这样调用会返回格式为 [object Type] 的字符串,其中 Type 是对象的内部类型。
b. .slice(8, -1)部分从返回的字符串中提取类型名称:
8: 从第8个字符开始截取(跳过前面的[object共8个字符)-1: 截取到倒数第1个字符(跳过最后的])
c. .toLowerCase()将提取的类型字符串转换为小写:
"Array"→"array""Number"→"number"
这一步不是必须的,但可以使返回结果更统一,方便后续比较。
3. 它能不能判断具体的对象类型?
在ES6中新增的Symbol.toStringTag中允许自定义 Object.prototype.toString 的返回值。
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
console.log(Object.prototype.toString.call(new MyClass())); // [object MyClass]
4. 特点与局限
- 覆盖所有类型:可以检测所有 JavaScript 内置类型,包括基本类型和引用类型
- 不受原型链影响:直接调用 Object 的原生方法,不会被对象重写的 toString 方法影响
- 一致性:不同环境、不同窗口/iframe 中行为一致
- 精确性:能准确区分 Array、Date、RegExp 等特殊对象类型
- 返回值:对于自定义对象,默认返回
[object Object] - 自定义:可以通过
Symbol.toStringTag自定义标签 - 缺陷:这种方法在性能方面比
type和instanceof稍慢,但通常差异可以忽略
5. 适用场景
- 需要精确检测任何数据类型时
- 区分不同内置对象类型(如 Array vs Object)
四、constructor 属性
1. 基本用法
通过访问对象的 constructor 属性可以获取其构造函数。
console.log([].constructor === Array); // true
console.log({}.constructor === Object); // true
console.log((123).constructor === Number); // true
function Person() {}
console.log(new Person().constructor === Person); // true
2. 特点与局限
- 可以检测基本类型和引用类型的构造函数
constructor属性容易被修改null和undefined没有constructor属性- 跨窗口/iframe 检测时会失效
3. 适用场景
- 快速检查对象是否由特定构造函数创建
- 需要获取对象构造函数时
五、Array.isArray
1. 基本用法
专门用于检测数组类型。
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
2. 特点与局限
- 专门用于数组检测,比
instanceof更可靠 - 解决了跨窗口/iframe 的数组检测问题
- 只能用于数组检测
3. 适用场景
- 需要专门检测数组类型时
六、其他专用检测方法
1. Number.isNaN
与全局的 isNaN 不同,Number.isNaN 只在值为 NaN 时返回 true。
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('str')); // false
console.log(isNaN('str')); // true (全局 isNaN 会先尝试转换为数字)
2. Number.isFinite
检测值是否为有限数字。
console.log(Number.isFinite(123)); // true console.log(Number.isFinite(Infinity)); // false
七、特殊数据类型检测
1. 检测 NaN
由于 NaN 是 JavaScript 中唯一不等于自身的值,可以利用这一特性检测:
function isNaN(value) {
return value !== value;
}
// 或者使用 Number.isNaN
console.log(Number.isNaN(NaN)); // true
2. 检测 null 或 undefined
function isNull(value) {
return value === null;
}
function isUndefined(value) {
return value === undefined;
}
// 检测 null 或 undefined
function isNil(value) {
return value == null; // == 下 null 和 undefined 相等
}
3. 检测原始包装对象
有时候需要区分原始值和其包装对象:
function isPrimitiveWrapper(obj) {
return (
obj instanceof Number ||
obj instanceof String ||
obj instanceof Boolean
);
}
八、Object.is和===的区别?
Object.is 和 === (严格相等运算符) 都是 JavaScript 中用于比较两个值是否相等的操作,但它们在处理某些特殊情况时有所不同。
相同点
- 都不会进行类型转换
- 对于大多数情况,两者的行为是一致的
主要区别
1. 对 NaN 的处理
NaN === NaN // false Object.is(NaN, NaN) // true
Object.is 认为两个 NaN 是相等的,而 === 认为它们不相等。
2. 对 +0 和 -0 的处理
+0 === -0 // true Object.is(+0, -0) // false
Object.is 区分 +0 和 -0,而 === 不区分。
3. 其他情况的比较
| 比较情况 | === 结果 | Object.is 结果 |
|---|---|---|
| undefined, undefined | true | true |
| null, null | true | true |
| true, true | true | true |
| false, false | true | true |
| 'foo', 'foo' | true | true |
| {}, {} | false | false |
| NaN, NaN | false | true |
| +0, -0 | true | false |
| 5, 5 | true | true |
| 5, '5' | false | false |
4. 何时使用
使用 === 作为日常比较的默认选择(更符合直觉,性能略优)
使用 Object.is 当需要:
- 明确区分 +0 和 -0
- 认为 NaN 等于 NaN
- 需要与 ES6 的
SameValue算法保持一致的行为
5. 实现原理
Object.is 的实现可以理解为:
function is(x, y) {
// 处理 +0 和 -0 的情况
if (x === 0 && y === 0) {
return 1 / x === 1 / y;
}
// 处理 NaN 的情况
if (x !== x) {
return y !== y;
}
// 其他情况使用严格相等
return x === y;
}
6. 总结
Object.is 提供了比 === 更精确的相等性判断,特别是在处理 JavaScript 中的特殊数值(NaN、+0/-0)时。在日常开发中,=== 仍然是首选,但在需要精确比较这些特殊值时,Object.is 是更好的选择。
九、综合比较
| 方法 | 基本类型 | 引用类型 | 自定义类型 | 跨窗口 | 备注 |
|---|---|---|---|---|---|
| typeof | 部分 | 有限 | 否 | 是 | null 返回 "object" |
| instanceof | 否 | 是 | 是 | 否 | 检查原型链 |
| Object.prototype.toString | 是 | 是 | 有限 | 是 | 最全面 |
| constructor | 是 | 是 | 是 | 否 | 属性易被修改 |
| Array.isArray | 否 | 仅数组 | 否 | 是 | 专门用于数组检测 |
十、实用工具函数
1. 通用类型检测函数
function getType(value) {
// 处理 null 和 undefined
if (value == null) {
return value === undefined ? 'undefined' : 'null';
}
// 处理其他类型
const type = typeof value;
if (type !== 'object') return type;
// 处理对象类型
const toString = Object.prototype.toString.call(value);
return toString.slice(8, -1).toLowerCase();
}
// 测试
console.log(getType(123)); // 'number'
console.log(getType('str')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType(null)); // 'null'
console.log(getType(undefined)); // 'undefined'
console.log(getType([])); // 'array'
console.log(getType({})); // 'object'
console.log(getType(new Date())); // 'date'
console.log(getType(/regex/)); // 'regexp'
console.log(getType(new Map())); // 'map'
2. 类型判断辅助函数
const is = {
// 基本类型
null: value => value === null,
undefined: value => value === undefined,
nil: value => value == null,
boolean: value => typeof value === 'boolean',
number: value => typeof value === 'number' && !Number.isNaN(value),
string: value => typeof value === 'string',
symbol: value => typeof value === 'symbol',
bigint: value => typeof value === 'bigint',
// 引用类型
object: value => value !== null && typeof value === 'object',
array: value => Array.isArray(value),
function: value => typeof value === 'function',
date: value => value instanceof Date,
regexp: value => value instanceof RegExp,
promise: value => value instanceof Promise,
set: value => value instanceof Set,
map: value => value instanceof Map,
weakset: value => value instanceof WeakSet,
weakmap: value => value instanceof WeakMap,
// 特殊值
nan: value => Number.isNaN(value),
finite: value => Number.isFinite(value),
primitive: value => Object(value) !== value,
// 自定义类型
instance: (value, constructor) => {
if (typeof constructor !== 'function') return false;
if (typeof value !== 'object' || value === null) return false;
return value instanceof constructor;
}
};
// 测试
console.log(is.array([])); // true
console.log(is.object({})); // true
console.log(is.null(null)); // true
console.log(is.instance(new Date(), Date)); // true
结语
JavaScript 提供了丰富的数据类型检测方法,每种方法都有其适用场景。在实际开发中:
- 对于基本类型检测,
typeof是最简单直接的方式 - 需要检测对象具体类型时,优先使用
Object.prototype.toString - 检测数组使用
Array.isArray - 检测自定义对象实例使用
instanceof或constructor - 对于复杂场景,可以组合使用多种方法或封装工具函数
理解这些检测方法背后的原理和差异,能够帮助我们在不同场景下选择最合适的检测方式,写出更健壮的 JavaScript 代码。
以上就是JavaScript数据检测方法的全面指南的详细内容,更多关于JavaScript数据检测方法的资料请关注脚本之家其它相关文章!
