一文搞懂JS三大类型检测:typeof、instanceof与Object.prototype.toString()
类型检测是JavaScript最基础也最容易踩坑的知识点。几乎每个前端开发者都遇到过"明明是数组,typeof却返回object"、"跨iframe后instanceof失效"等问题。
本文将从底层原理出发,彻底搞懂
typeof、instanceof和Object.prototype.toString()三者的区别、坑点和最佳实践,让你再也不怕面试问类型检测。
一、为什么我们需要类型检测?
JavaScript是一门弱类型动态语言,变量的类型在运行时才能确定。这给了我们极大的灵活性,但也带来了很多潜在的问题:
javascript
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 3 ✅
console.log(add('1', '2')); // '12' ❌ 不是我们想要的结果
如果没有类型检测,我们无法保证函数参数的类型,很容易出现意料之外的bug。因此,掌握正确的类型检测方法是每个前端开发者的基本功。
JavaScript提供了三种最常用的类型检测方法:
typeof:检测基本数据类型instanceof:检测引用类型的原型关系Object.prototype.toString():通用类型检测,终极解决方案
二、typeof:基础类型检测首选
1. 核心定义
typeof是一个一元运算符 ,用于检测一个值的基本数据类型 。它返回一个小写字符串,表示该值的类型。
2. 基本语法
javascript
typeof value;
// 或者(括号可选,typeof是运算符不是函数)
typeof(value);
3. 正确的检测结果
typeof能准确检测以下7种类型:
| 输入值 | 返回结果 |
|---|---|
'hello' |
'string' |
123 |
'number' |
true |
'boolean' |
undefined |
'undefined' |
Symbol('id') |
'symbol' |
123n |
'bigint' |
function() {} |
'function' |
4. 著名的历史bug:typeof null === 'object'
这是JavaScript诞生以来最著名的bug,也是面试必考题:
javascript
console.log(typeof null); // 'object' ❌ 正确应该是 'null'
历史原因:
- JavaScript最初的版本使用32位系统,为了性能优化,用低位存储类型信息
000代表对象类型,而null的二进制表示全是0,所以被错误地识别为对象- 这个bug存在了几十年,因为修复它会导致无数现有代码崩溃,所以永远不会被修复
5. 最大的局限性:无法区分引用类型
除了函数之外,typeof对所有引用类型都返回'object',无法进一步区分:
javascript
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object' ❌ 无法区分数组和对象
console.log(typeof new Date()); // 'object'
console.log(typeof new RegExp()); // 'object'
console.log(typeof new Map()); // 'object'
console.log(typeof new Set()); // 'object'
6. 其他特殊行为
javascript
// 未声明的变量不会报错,返回 'undefined'
console.log(typeof a); // 'undefined'(a没有声明)
// NaN 是 number 类型
console.log(typeof NaN); // 'number'
// Infinity 是 number 类型
console.log(typeof Infinity); // 'number'
三、instanceof:引用类型检测利器
1. 核心定义
instanceof是一个二元运算符 ,用于检测一个对象 是否是某个构造函数的实例。它的原理是:
判断构造函数的
prototype属性是否出现在该对象的原型链上。
2. 基本语法
javascript
object instanceof Constructor;
- 左边必须是一个对象 (如果是基本类型,会返回
false) - 右边必须是一个构造函数 (如果不是函数,会抛出
TypeError) - 返回一个布尔值:
true或false
3. 基本用法示例
javascript
// 检测数组
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true(数组也是对象)
// 检测普通对象
const obj = { name: '张三' };
console.log(obj instanceof Object); // true
// 检测自定义类
class Person {}
const p = new Person();
console.log(p instanceof Person); // true
console.log(p instanceof Object); // true
// 检测内置对象
const date = new Date();
console.log(date instanceof Date); // true
console.log(date instanceof Object); // true
4. 底层原理详解
instanceof的内部实现逻辑可以用以下伪代码表示:
javascript
function myInstanceof(obj, Constructor) {
// 基本类型直接返回 false
if (obj === null || typeof obj !== 'object') {
return false;
}
// 获取对象的原型
let proto = Object.getPrototypeOf(obj);
// 沿着原型链向上查找
while (proto !== null) {
if (proto === Constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
这就是为什么arr instanceof Object会返回true:因为Array.prototype.__proto__ === Object.prototype,Object.prototype出现在了数组的原型链上。
5. 三大致命局限性
局限性1:不能检测基本类型
instanceof对所有基本类型都返回false,因为基本类型不是对象:
javascript
console.log(123 instanceof Number); // false
console.log('hello' instanceof String); // false
console.log(true instanceof Boolean); // false
console.log(null instanceof Object); // false
console.log(undefined instanceof Object); // false
⚠️ 注意:用new关键字创建的包装类型是对象,会返回true:
javascript
console.log(new Number(123) instanceof Number); // true
console.log(new String('hello') instanceof String); // true
局限性2:跨iframe/窗口会失效
这是一个非常隐蔽但致命的坑。在浏览器中,每个iframe都有自己独立的全局环境,拥有自己的构造函数。如果一个对象是在iframe A中创建的,那么在iframe B中用instanceof检测会返回false:
javascript
// 主页面
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
// 获取 iframe 中的 Array 构造函数
const iframeArray = iframe.contentWindow.Array;
// 在主页面创建数组
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true(主页面的 Array)
console.log(arr instanceof iframeArray); // false ❌(iframe 的 Array)
局限性3:原型链被修改后会失效
如果手动修改了对象的原型或者构造函数的prototype,instanceof的结果会发生变化:
javascript
const obj = {};
console.log(obj instanceof Object); // true
// 修改对象的原型
Object.setPrototypeOf(obj, null);
console.log(obj instanceof Object); // false ❌
四、Object.prototype.toString():类型检测的终极武器
1. 核心定义
Object.prototype.toString()是定义在Object原型上的一个方法,它的唯一设计目的就是:
返回一个表示该对象的内部类型标识的字符串。
它的返回值格式永远是固定的:
arduino
"[object Xxx]"
其中Xxx就是该对象的内部类型名称,首字母大写。
2. 为什么必须用call()调用?
这是最常见的一个问题:为什么不能直接写obj.toString(),而必须写Object.prototype.toString.call(obj)?
答案:因为几乎所有内置对象都重写了自己的toString()方法。
javascript
const arr = [1, 2, 3];
const date = new Date();
const num = 123;
// 重写后的 toString() 返回的是内容,不是类型
console.log(arr.toString()); // "1,2,3"
console.log(date.toString()); // "Wed May 13 2026 10:00:00 GMT+0800"
console.log(num.toString()); // "123"
// 调用原始的 Object.prototype.toString() 才会返回类型
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(date)); // "[object Date]"
console.log(Object.prototype.toString.call(num)); // "[object Number]"
Object.prototype.toString.call(obj)的本质是:强制调用原始的、未被重写的toString()方法,获取对象的真实内部类型。
3. 底层原理:为什么它这么准?
Object.prototype.toString()的准确性来自于JavaScript语言规范的强制规定。
- ES5时代 :基于
[[Class]]内部属性,这是一个不可枚举、不可配置、不可修改的底层属性,对象创建时就被引擎设置好 - ES6时代 :基于
Symbol.toStringTag,向后兼容旧的[[Class]]属性,同时允许开发者自定义类型标识
它读取的是对象最底层的、无法被修改的类型标识,所以永远不会出错。
4. 所有内置类型的完整返回值表
下面是所有常见内置类型的返回值,建议收藏:
| 输入值 | 返回结果 |
|---|---|
'hello' |
'[object String]' |
123 |
'[object Number]' |
true |
'[object Boolean]' |
undefined |
'[object Undefined]' |
null |
'[object Null]' |
Symbol('id') |
'[object Symbol]' |
123n |
'[object BigInt]' |
function() {} |
'[object Function]' |
{} |
'[object Object]' |
[] |
'[object Array]' |
new Date() |
'[object Date]' |
/abc/g |
'[object RegExp]' |
new Error() |
'[object Error]' |
new Map() |
'[object Map]' |
new Set() |
'[object Set]' |
new Promise(() => {}) |
'[object Promise]' |
5. 三大核心优势(碾压前两者)
优势1:能准确区分所有内置类型
解决了typeof无法区分引用类型的问题:
javascript
// typeof 无法区分
console.log(typeof []); // 'object'
console.log(typeof {}); // 'object'
console.log(typeof new Date()); // 'object'
// Object.prototype.toString 可以准确区分
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:跨iframe/窗口100%有效
解决了instanceof跨iframe失效的问题:
javascript
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
const arr = [1, 2, 3];
console.log(arr instanceof iframeArray); // false ❌
console.log(Object.prototype.toString.call(arr)); // "[object Array]" ✅
优势3:不受原型链修改的影响
解决了instanceof原型链修改后失效的问题:
javascript
const obj = {};
Object.setPrototypeOf(obj, null);
console.log(obj instanceof Object); // false ❌
console.log(Object.prototype.toString.call(obj)); // "[object Object]" ✅
6. 唯一的局限性
Object.prototype.toString()不是万能的,它有一个无法解决的局限性:
无法区分自定义类的实例。
所有自定义类的实例,都会返回"[object Object]":
javascript
class Person {}
const p = new Person();
console.log(Object.prototype.toString.call(p)); // "[object Object]" ❌
console.log(p instanceof Person); // true ✅
五、三者核心区别对比表
| 对比维度 | typeof |
instanceof |
Object.prototype.toString() |
|---|---|---|---|
| 检测目标 | 基本数据类型 | 引用类型的原型关系 | 所有内置类型 |
| 原理 | 检测值的底层类型标记 | 遍历原型链查找构造函数的prototype | 读取对象的内部类型标识 |
| 返回值 | 小写字符串 | 布尔值 | "[object Xxx]"格式字符串 |
| 基本类型 | 能准确检测(除了null) | 全部返回false | 能准确检测所有基本类型 |
| 引用类型 | 只能区分function,其他都返回'object' | 能区分不同的引用类型 | 能准确区分所有内置引用类型 |
| 自定义类 | 无法区分 | 能准确检测 | 无法区分 |
| 跨iframe | 不受影响 | 会失效 | 不受影响 |
| 原型链修改 | 不受影响 | 会失效 | 不受影响 |
| 实现难度 | 简单 | 中等 | 简单 |
| 适用场景 | 快速检测基本类型 | 检测自定义类实例 | 检测所有内置类型 |
六、99%的前端都踩过的坑
坑1:用typeof检测数组
javascript
// ❌ 错误
if (typeof arr === 'array') {
// 永远不会执行
}
// ✅ 正确
if (Array.isArray(arr)) {
// 推荐,跨iframe也能正常工作
}
坑2:用instanceof检测基本类型
javascript
// ❌ 错误
if (num instanceof Number) {
// 永远不会执行
}
// ✅ 正确
if (typeof num === 'number') {
// 检测基本类型数字
}
坑3:用typeof null === 'object'判断null
javascript
// ❌ 错误
if (typeof obj === 'object') {
// 会把 null 也包含进来
}
// ✅ 正确
if (obj !== null && typeof obj === 'object') {
// 真正的对象
}
// ✅ 正确(判断null)
if (obj === null) {
// 只有null会满足这个条件
}
坑4:跨iframe环境下用instanceof检测数组
javascript
// ❌ 错误(跨iframe会失效)
if (arr instanceof Array) {
// 可能不会执行
}
// ✅ 正确
if (Array.isArray(arr)) {
// 永远正确
}
七、终极通用类型检测方案
基于Object.prototype.toString(),我们可以封装一个最完善的通用类型检测函数,覆盖所有常见场景:
javascript
/**
* 通用类型检测函数
* @param {*} value - 需要检测的值
* @returns {string} 小写的类型名称
*/
function getType(value) {
// 先处理 null 和 undefined,避免装箱
if (value === null) return 'null';
if (value === undefined) return 'undefined';
// 对于其他类型,使用 Object.prototype.toString
const typeStr = Object.prototype.toString.call(value);
// 提取 "[object Xxx]" 中的 Xxx 部分,并转为小写
return typeStr.slice(8, -1).toLowerCase();
}
// 使用示例
console.log(getType(123)); // 'number'
console.log(getType(null)); // 'null'
console.log(getType([])); // 'array'
console.log(getType(new Date())); // 'date'
console.log(getType(new Map())); // 'map'
八、最佳实践总结
-
检测基本类型 :优先使用
typeofjavascriptif (typeof str === 'string') { ... } if (typeof num === 'number') { ... } -
检测null :直接使用
=== nulljavascriptif (obj === null) { ... } -
检测数组 :优先使用
Array.isArray()(跨iframe安全)javascriptif (Array.isArray(arr)) { ... } -
检测自定义类实例 :使用
instanceofjavascriptif (p instanceof Person) { ... } -
检测内置引用类型 :优先使用
getType(value)(跨iframe安全)javascriptif (getType(date) === 'date') { ... } if (getType(map) === 'map') { ... } -
永远不要:
- 用
typeof检测数组、Date、RegExp等引用类型 - 用
instanceof检测基本类型 - 依赖
typeof null === 'object'这个bug - 在跨iframe环境下使用
instanceof检测内置类型
- 用
九、面试高频考点总结
typeof null为什么返回'object'?(历史bug)typeof和instanceof的区别是什么?- 如何准确检测一个值是不是数组?(
Array.isArray()优于instanceof) instanceof的原理是什么?(原型链查找)instanceof跨iframe为什么会失效?(不同窗口的构造函数是不同的对象)- 为什么
Object.prototype.toString.call()能准确检测所有类型?(读取内部类型标识) - 如何实现一个通用的类型检测函数?(基于
Object.prototype.toString.call())
写在最后
类型检测是JavaScript的基础中的基础,但也是最容易被忽视的知识点。很多看似复杂的bug,追根溯源都是因为类型检测错误导致的。
希望这篇文章能帮你彻底搞懂这三个方法,让你在实际开发中写出更健壮的代码,在面试中从容应对所有类型检测相关的问题。
如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,有任何问题可以在评论区留言讨论!