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>