开箱秘籍,一招鲜吃遍天的Object.prototype.toString.call

发布时间:2024年01月05日

WX20231120-135736@2x.png

在前端开发中,精准的数据类型判断是每一位开发者都必不可少的技能。就像熟知的 typeof 操作符,但在面对复杂数据类型时,仍然存在着局限性。

本文将深入剖析各类数据类型判断方法,特别聚焦于 Object.prototype.toString.call 这一备受推崇的技术。通过对比分析,剖析它的独特之处,以及为何它在实际应用中备受青睐。

此外,文章将全面介绍 JavaScript 中各种数据类型的判断技巧。无论你是在项目实践中,还是在应对面试官的提问,这篇内容都将成为你的得力助手。

JavaScript数据类型

在JavaScript中,数据类型可以分为基础数据类型复杂数据类型

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

基础数据类型是不可变的,而复杂数据类型是可变的。

这意味着对于基础数据类型,一旦它们被创建,它们的值就不能被修改。当你对基础数据类型的变量进行操作时,实际上是创建了一个新的值。而复杂数据类型(对象和数组)在引用上是可变的,修改对象或数组会影响到引用它们的地方。它们存储的是引用,而不是实际的值。当你修改对象或数组时,实际上是在修改存储在变量中的引用,而不是创建一个新的引用。

注意:在复杂数据类型中,可能会导致在不同部分之间共享相同对象或数组的引用,因此一个地方的修改会影响到所有引用该对象或数组的地方。

数据类型判断

typeof 操作符

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

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 无法区分它们。

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

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

instanceof 操作符

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

function CustomType() {}

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

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

// 复杂数据类型
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 检查对象类型时,得到的结果在另一个框架中可能不同。

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

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

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

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

console.log(Object.prototype.toString.call(objA) === '[object MyClass]');

constructor 属性

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

const obj = {}; 
console.log(obj.constructor === Object); // 返回 true

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

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

const obj = {};

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

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

当然,对于基础数据数据类型来说,由于它们不是对象,因此并没有 constructor 属性。试图访问基本数据类型的 constructor 属性会导致 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 " 和 “]”,后接具体的数据类型。

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]",结果的字符串较长,方法的使用也是需要写很长的字符,但是我们可以封装一个函数,专门在开发中判断处理:

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 在处理多窗口环境中的问题。

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

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

image.png

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

特殊情况的处理

NaN 的判断

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

console.log(isNaN(42));      // 返回 false
console.log(isNaN("Hello")); // 返回 true

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

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

null 和 undefined 的判断

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

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

注意事项:

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

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 属性在某些情况下可能被修改。

image.png

4. 性能考虑

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

原理剖析 Object.prototype.toString.call

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

Object.prototype.toString.call(arg)

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

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 来判断对象的具体类型。

WX20231120-135825@2x.png

总结

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

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

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

文章来源:https://blog.csdn.net/qq_37834631/article/details/135402545
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。