Object.prototype.toString.call()方法的实现原理

693 阅读2分钟

Object.prototype.toString.call()方法的实现原理

一、为什么要使用 call

对于 Object.prototype.toString 方法,会返回一个形如"[object XXX]"的字符串 如果对象的 toString()方法未被重写,就会返回形如上面形式的字符串。

  console.log({}.toString()); //  [object Object]
  console.log(Math.toString()); // [object Math]

但是,大多数对象对于toString()方法都重写了,因此这时需要使用call()方法来调用未被重写的toStringf方法。

<script>
  let obj = {
    toString() {
      return "X";
    },
  };

  console.log(obj.toString()); // 返回X
  console.log(Object.prototype.toString.call(obj)); // 返回[object Object]
</script>

二、参数问题

对于Object.prototype.toString.call(arg),若参数是null或者undefined,直接返回结果。

  console.log(Object.prototype.toString.call(null)); //[object Null]
  console.log(Object.prototype.toString.call(undefined)); //[object Undefined]

若参数不为null和undefined,则将参数先转为对象,在做判断。

对于基本数据类型,转为对象的方法即包装类。

注意:转为对象后,会取得该对象的[Symbol.toStringTag]属性值(可能会遍历原型链)作为tag,如果没有该属性或属性值不为字符串类型,则依照下表取得tag,然后返回"[object" + tag + "]"形式的字符串,而这个字符串便可以显示的告诉你数据是哪种数据类型,不会像typeof方法那样无法准确区分数组和对象,也不会像instanceof方法那样去猜测数据类型是哪种类型。

// Boolean 类型,tag 为 "Boolean"
Object.prototype.toString.call(true);            // => "[object Boolean]"

// Number 类型,tag 为 "Number"
Object.prototype.toString.call(1);               // => "[object Number]"

// String 类型,tag 为 "String"
Object.prototype.toString.call("");              // => "[object String]"

// Array 类型,tag 为 "String"
Object.prototype.toString.call([]);              // => "[object Array]"

// Arguments 类型,tag 为 "Arguments"
Object.prototype.toString.call((function() {
  return arguments;
})());                                           // => "[object Arguments]"

// Function 类型, tag 为 "Function"
Object.prototype.toString.call(function(){});    // => "[object Function]"

// Error 类型(包含子类型),tag 为 "Error"
Object.prototype.toString.call(new Error());     // => "[object Error]"

// RegExp 类型,tag 为 "RegExp"
Object.prototype.toString.call(/\d+/);           // => "[object RegExp]"

// Date 类型,tag 为 "Date"
Object.prototype.toString.call(new Date());      // => "[object Date]"

// 其他类型,tag 为 "Object"
Object.prototype.toString.call(new class {});    // => "[object Object]"

下面是部署了Symbol.toStringTag的例子。可以看出,属性值期望是一个字符串,否则会被忽略。

  var o1 = {
    [Symbol.toStringTag]: "A",
  };

  var o2 = {
    [Symbol.toStringTag]: null,
  };

  console.log(Object.prototype.toString.call(o1)); // [object A]
  console.log(Object.prototype.toString.call(o2)); // [object Object]

当然,Symbol.toStringTag属性也是可以部署在原型链上的:

class A {}
A.prototype[Symbol.toStringTag] = "A";
console.log(Object.prototype.toString.call(new A())); // [object A]

而symbol这种基本数据类型也正是因为在其原型链上部署了这个属性,因而能正确通过此方法进行正确数据类型判断

console.log(Object.prototype.toString.call(Symbol())); //"[object Symbol]"

三、实现Object.prototype.toString.call()方法

<script>
  // 实现 Object.prototype.toString.call()检测数据类型
  let getObjType = (obj) => {
    let toString = Object.prototype.toString;

    let map = {
      "[object Boolean]": "boolean",
      "[object String]": "string",
      "[object Number]": "number",
      "[object Null]": "null",
      "[object Undefined]": "undefined",
      "[object Object]": "object",
      "[object Function]": "function",
      "[object Date]": "date",
      "[object RegExp]": "regExp",
      "[object Array]": "array",
    };

    if (obj instanceof Element) {
      return "element";
    }

    return map[toString.call(obj)];
  };

  let res = getObjType(2);
  let res1 = getObjType({});
  let res2 = getObjType(function () {});
  console.log(res);
  console.log(res1);
  console.log(res2);
</script>