一招鲜吃遍天,最可靠的Object.prototype.toString.call

JavaScript中包含多种数据类型,在开发中,我们常常需要对数据类型进行准确的判断。如我们熟悉的 typeof 操作符,简单直观,但它在处理复杂数据类型时却存在一些限制。

那么,为什么我说Object.prototype.toString.call,在数据类型判断中是最可靠方法呢?在接下来的内容中,我将详细对比探讨不同的数据类型判断方法,并解释为何Object.prototype.toString.call在这方面是一种更为可靠的解决方案。

常规操作

先简单说下基础数据类型和负载数据类型都包括哪几类。

  • 基础数据类型:字符串(String)、数字(Number)、布尔(Boolean)、Undefined、Null 和 Symbol。
  • 复杂数据类型:对象(Object)、数组(Array)、函数(Function)。

typeof 操作符

JavaScript 中最常见的数据类型判断方式之一是使用 typeof 操作符。该操作符返回一个字符串,表示给定变量的数据类型。在处理基础数据类型时,typeof 是一个简单而直观的选择。

javascript 复制代码
console.log(typeof "Hello");   // 返回 "string"
console.log(typeof 123);        // 返回 "number"
console.log(typeof true);      // 返回 "boolean"
console.log(typeof undefined); // 返回 "undefined"

typeof 是一种简单的方式,特别适用于对基础数据类型的判断。即使变量未被声明,使用 typeof 也不会引发错误。

但是, typeof 在判断数据类型时存在一些限制。首先,它不适用于判断 null ,因为 typeof null 返回 "object",这是 JavaScript 语言本身的一个错误导致。其次,也不适用于复杂数据类型 ,如数组、对象等,typeof 无法区分它们。

javascript 复制代码
console.log(typeof [1, 2, 3]);    // 返回 "object"
console.log(typeof { acb: 123 }); // 返回 "object"
console.log(typeof null);         // 返回 "object"(历史上的一个 bug)

为了更准确地判断复杂数据类型,我们需要另外一种方式。。。

instanceof 操作符

instanceof 是 JavaScript 中用于检查对象是否是特定类型(或特定类型的实例)的操作符。它适用于自定义对象类型instanceof 对于自定义对象类型的判断非常有效。

javascript 复制代码
function CustomType() {}

var ctype = new CustomType();
console.log(ctype instanceof CustomType); // 返回 true,表示 ctype 是 CustomType 类型的实例

但是,instanceof基本数据类型复杂数据类型,表现不佳!!!

javascript 复制代码
// 复杂数据类型
const obj = {}; 
console.log(obj instanceof Object); // 返回 true
console.log([1, 2, 3] instanceof Array); // 返回 true,因为数组是对象的一种,Array 是其构造函数 
console.log([1, 2, 3] instanceof Object); // 返回 true,数组也是 Object 类的实例

// 基础数据类型
console.log('Hello' instanceof String);  // 返回 false,因为字符串是基本数据类型,不是 String 类的实例
console.log(true instanceof Boolean); // 返回 false,原因同上
console.log(123 instanceof Number);  // 返回 false,原因同上

// 如果想要使用,需要通过构建函数包装。。。
const str = new String('Hello');
console.log(str instanceof String); // 返回 true,是的你没看错,是不是不方便
const num = new Number(123);
console.log(num instanceof Number); // 返回 true,是的你没看错,是不是不方便

instanceof 还存在多重引用问题 ,在 JavaScript 中,不同框架或窗口拥有各自的全局上下文,从而导致 instanceof 的不确定性。

当对象在一个框架中创建,并被传递到另一个框架中时,instanceof 的结果可能受到影响,因为每个框架都有自己的构造函数和原型链。这可能导致在一个框架中使用 instanceof 检查对象类型时,得到的结果在另一个框架中可能不同。

javascript 复制代码
// 在框架 A 中定义一个构造函数 
function MyClass() {} 

// 在框架 A 中创建一个对象实例 
const obj = new MyClass();

// 将对象传递到框架 B 中
console.log(objA instanceof MyClass); 
// 在框架 A 中返回 true
// 在框架 B 中返回 false。这是因为在框架 B 中,MyClass 的构造函数和原型链是不同的。

解决这个问题的一种方法是使用 Object.prototype.toString.call,它不依赖于具体的构造函数或原型链,而是直接检查对象的内部标识。

javascript 复制代码
console.log(Object.prototype.toString.call(objA) === '[object MyClass]');

constructor 属性

constructor 是 JavaScript 中对象的一个属性,它指向对象的构造函数。可以通过检查对象的 constructor 属性,判断数据类型。constructor 属性的优势是能直观地表示对象的构造函数。

javascript 复制代码
const obj = {}; 
console.log(obj.constructor === Object); // 返回 true

function CustomType() {}
var obj = new CustomType();
console.log(obj.constructor === CustomType); // 返回 true

但是在某些情况下,constructor 属性可能被修改(如序列化、反序列化JSON处理),判断不准确。其次对于基本数据类型,constructor 无法提供有效的判断。

javascript 复制代码
const obj = {};

// 修改 constructor 属性
obj.constructor = function customObjConstructor() {};

console.log(obj.constructor === Object);  // 返回 false
console.log(obj.constructor === customObjConstructor);  // 返回 true

当然,对于基础数据数据类型来说,由于它们不是对象,因此并没有 constructor 属性。试图访问基本数据类型的 constructor 属性会导致 JavaScript 临时将其包装为相应的对象类型,然后访问其构造函数。

javascript 复制代码
const num = 123;
// JavaScript 临时将基本数据类型包装为 Number 对象
console.log(num.constructor === Number);  // 返回 true

const str = 'Hello';
// JavaScript 临时将基本数据类型包装为 String 对象
console.log(str.constructor === String);  // 返回 true

在我们开发时候,为了处理数据类型,尤其是为了规遍掉所有可能的情况,以上方法可能存在一些不足。那么我们来看看 Object.prototype.toString.call() 方法的使用和优势。

Object.prototype.toString.call()

Object.prototype.toString.call() 方法是最可靠、最全面的数据类型判断方式。该方法返回一个表示对象类型的字符串,包含 "[object " 和 "]",后接具体的数据类型。

javascript 复制代码
console.log(Object.prototype.toString.call("Hello"));   // 返回 "[object String]"
console.log(Object.prototype.toString.call(123));        // 返回 "[object Number]"
console.log(Object.prototype.toString.call(true));      // 返回 "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // 返回 "[object Undefined]"
console.log(Object.prototype.toString.call(null));      // 返回 "[object Null]"
console.log(Object.prototype.toString.call([1, 2, 3]));       // 返回 "[object Array]"

优势非常明显:

  • 适用于所有数据类型: Object.prototype.toString.call() 方法几乎可以适用于所有可能的数据类型,包括基本数据类型和复杂数据类型。
  • 不易受篡改:constructor 属性相比,Object.prototype.toString.call() 方法不容易被篡改,因此更加可靠。

如果说缺点的话,也就是呈现不直观"[object Array]",结果的字符串较长,方法的使用也是需要写很长的字符,但是我们可以封装一个函数,专门在开发中判断处理:

javascript 复制代码
export function getDataType(value) { 
    return Object.prototype.toString.call(value).slice(8, -1); 
}

// 示例用法 
getDataType("Hello"); // 返回 "String" 
getDataType(42); // 返回 "Number" 
getDataType(true); // 返回 "Boolean" 
getDataType(undefined); // 返回 "Undefined" 
getDataType(null); // 返回 "Null" 
getDataType([1, 2, 3]); // 返回 "Array"
getDataType(() => {}); // 返回 "Function"

Array.isArray() 方法

Array.isArray() 是专门用于判断对象是否为数组的方法。它是 ECMAScript 5 引入的,用于解决 instanceof 在处理多窗口环境中的问题。

javascript 复制代码
var arr = [1, 2, 3];
console.log(Array.isArray(arr)); // 返回 true

大家也可以使用它来判断数组,很直观,但是不适用于其他数据类型 ,因为 Array.isArray() 只能用于判断数组。

在处理复杂数据类型时,尤其是需要覆盖多种情况的判断时,Object.prototype.toString.call() 还是更可靠的解决方案。

特殊情况的处理

NaN 的判断

NaN 是一个特殊的数值,代表非数值。在 JavaScript 中,可以使用 isNaN() 函数来判断一个值是否是 NaN。适用于 NaN 判断,直接、简单的方式来判断一个值是否是 NaN

javascript 复制代码
console.log(isNaN(42));      // 返回 false
console.log(isNaN("Hello")); // 返回 true

注意事项:isNaN() 对于数字字符串的处理可能导致一些意外的结果。在需要判断是否为数字时,最好先将字符串转为数字再进行判断。

javascript 复制代码
console.log(isNaN("42")); // 返回 false,因为 "42" 被隐式转换为数字 42

null 和 undefined 的判断

在 JavaScript 中,nullundefined 是两个特殊的值,表示缺失或未定义。

javascript 复制代码
// 判断变量是否为 null 或 undefined
if (data === null || typeof data === 'undefined') {
    // 处理 null 或 undefined 的情况
}

注意事项:

在一些情况下,可以使用 == 来判断变量是否为 nullundefined,但要谨慎使用,以避免类型转换带来的意外行为。

javascript 复制代码
console.log(variable == 'undefined'); // 注意类型转换

// 最好采用 typeof 或者 Object.prototype.toString.call() 来判断
console.log(typeof undefinedVariable === 'undefined');
console.log(Object.prototype.toString.call(variable) === '[object Undefined]');

实际情况下,我们如何选择?

在选择 JavaScript 数据类型判断的方法时,也需要综合考虑多个因素。

1. 方法的适用范围

基础数据类型: 针对基础数据类型的判断,typeof 可以提供简单的方式,但需要注意其在判断 null 时的限制。

复杂数据类型: 对于复杂数据类型,尤其是数组和对象,Object.prototype.toString.call() 提供了更全面、更可靠的判断方式。

2. 可维护性和可读性

使用直观、清晰的方法可以提高代码的可读性。例如,Array.isArray() 在判断数组时提供了更直观的方式。我认为代码中封装Object.prototype.toString.call(),也是可读性很高的~~

3. 可靠性

考虑方法的稳定性,Object.prototype.toString.call() 在多种情况下表现更为稳定,不易受到环境和数据的影响,比如constructor 属性在某些情况下可能被修改。

4. 性能考虑

在大规模数据判断时,对于性能而言,Object.prototype.toString.call() 相对于 typeofArray.isArray() 可能会稍显短板,因为它执行了更多的操作,包括字符串的拼接和截取。然而,这种差异在实际应用中并不总是非常显著,而且 Object.prototype.toString.call() 在提供更准确的类型信息上有其独特的优势。

Object.prototype.toString.call 原理剖析

Object.prototype.toString.call 是一个 JavaScript 方法,它可以用来获取对象的类型。它的语法是:

javascript 复制代码
Object.prototype.toString.call(arg)

其中 arg 是要检查的对象。这个方法会返回一个形如 "[object Type]" 的字符串,其中 Type 是对象的类型。例如:

javascript 复制代码
Object.prototype.toString.call("hello") // => "[object String]"
Object.prototype.toString.call([]) // => "[object Array]"

为什么说 Object.prototype.toString.call() 可以得到最可靠的数据类型呢?

因为它可以获取对象的内部 [[Class]] 属性,这个属性是一个字符串,表示对象的类型。这个属性是在对象创建时就确定的,不会随着对象的变化而变化。Object.prototype.toString.call 可以通过 call 方法,将任意对象作为 this 参数传入,然后返回该对象的 [[Class]] 属性值,形如 "[object Type]" 的字符串。

这样我们就可以根据不同的 Type 来判断对象的具体类型。

总结

平时我们开发代码时候,需要注意代码的可维护性和可读性,毕竟越清晰简洁的代码对我们越有利,另外我们也需要考虑方法的稳定性,确保判断结果的准确性,这里再次对判断数据类型方法做个总结:

  • typeof 操作符: 提供了简单直观的方式,特别适用于基础数据类型的判断。然而,在处理复杂数据类型和判断 null 时存在一些限制。
  • instanceof 操作符: 用于判断对象是否属于特定类型,对于自定义对象类型较为有效,但在处理基础数据类型和多全局上下文时存在一些问题。
  • constructor 属性: 指向对象的构造函数,提供直观的方式判断对象类型。然而,易受篡改且不适用于基础数据类型。
  • Object.prototype.toString.call(): 认为是最可靠的数据类型判断方式,适用于几乎所有数据类型,包括基础和复杂数据类型。
  • Array.isArray(): 专用于判断对象是否为数组,提供直观的方式。在处理其他复杂数据类型时无法提供准确信息。

这里我还是强烈推荐Object.prototype.toString.call()。当然,在需要考虑性能的场景下,选择更高效的方法,就另说了~

你认为呢,欢迎各位伙伴在评论区发表自己的想法,大家一起探讨下!!!

相关推荐
无双_Joney18 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥20 分钟前
前端必学的 CSS Grid 布局体系
前端·css
EMT20 分钟前
在 Vue 项目中使用 URL Query 保存和恢复搜索条件
javascript·vue.js
ccnocare21 分钟前
选择文件夹路径
前端
艾小码21 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月22 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁26 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅26 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸28 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端