typeof、instanceof与Object.prototype.toString()

一文搞懂JS三大类型检测:typeof、instanceof与Object.prototype.toString()

类型检测是JavaScript最基础也最容易踩坑的知识点。几乎每个前端开发者都遇到过"明明是数组,typeof却返回object"、"跨iframe后instanceof失效"等问题。

本文将从底层原理出发,彻底搞懂typeofinstanceofObject.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
  • 返回一个布尔值:truefalse

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.prototypeObject.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:原型链被修改后会失效

如果手动修改了对象的原型或者构造函数的prototypeinstanceof的结果会发生变化:

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'

八、最佳实践总结

  1. 检测基本类型 :优先使用typeof

    javascript 复制代码
    if (typeof str === 'string') { ... }
    if (typeof num === 'number') { ... }
  2. 检测null :直接使用=== null

    javascript 复制代码
    if (obj === null) { ... }
  3. 检测数组 :优先使用Array.isArray()(跨iframe安全)

    javascript 复制代码
    if (Array.isArray(arr)) { ... }
  4. 检测自定义类实例 :使用instanceof

    javascript 复制代码
    if (p instanceof Person) { ... }
  5. 检测内置引用类型 :优先使用getType(value)(跨iframe安全)

    javascript 复制代码
    if (getType(date) === 'date') { ... }
    if (getType(map) === 'map') { ... }
  6. 永远不要

    • typeof检测数组、Date、RegExp等引用类型
    • instanceof检测基本类型
    • 依赖typeof null === 'object'这个bug
    • 在跨iframe环境下使用instanceof检测内置类型

九、面试高频考点总结

  1. typeof null为什么返回'object'?(历史bug)
  2. typeofinstanceof的区别是什么?
  3. 如何准确检测一个值是不是数组?(Array.isArray()优于instanceof
  4. instanceof的原理是什么?(原型链查找)
  5. instanceof跨iframe为什么会失效?(不同窗口的构造函数是不同的对象)
  6. 为什么Object.prototype.toString.call()能准确检测所有类型?(读取内部类型标识)
  7. 如何实现一个通用的类型检测函数?(基于Object.prototype.toString.call()

写在最后

类型检测是JavaScript的基础中的基础,但也是最容易被忽视的知识点。很多看似复杂的bug,追根溯源都是因为类型检测错误导致的。

希望这篇文章能帮你彻底搞懂这三个方法,让你在实际开发中写出更健壮的代码,在面试中从容应对所有类型检测相关的问题。

如果觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,有任何问题可以在评论区留言讨论!

相关推荐
Highcharts.js1 小时前
Highcharts React v5升级三问|最大的升级方向是什么?需要注意什么?有什么优化?
前端·javascript·react.js·前端框架·highcharts·大数据渲染·前端性能
129y1 小时前
JS入门参考:引擎、作用域与let/const,一起慢慢理解~
javascript
代码煮茶1 小时前
Vue3 权限系统实战 | 从 0 搭建完整 RBAC 权限管理
前端·javascript·vue.js
前端小木屋1 小时前
Node基础入门
javascript·node.js
山河木马2 小时前
Emscripten 从 C/C++ 调用 JavaScript
前端·javascript·c++
scan7243 小时前
pydantic格式输出
服务器·前端·javascript
ZC跨境爬虫3 小时前
跟着MDN学HTML_day44:(ProcessingInstruction接口)
前端·javascript·ui·html·媒体
ZC跨境爬虫4 小时前
跟着MDN学HTML_day_45:(EventTarget接口)
前端·javascript·ui·html·媒体
漂移的电子4 小时前
【el-tree】外层多选,某个属性内层单选
前端·javascript·vue.js