Object.prototype.toString
最近在看 Lodash 的源码,其精简的语法和巧妙的设计,值得大家去细品 。其中有一个工具函数叫 getTag,旨在获取对象的类型标记(Tag),即我们所熟知的,利用 Object.prototype.toString.call() 去做类型检测。
function getTag(value) {
if (value == null) { // 执行非严格相等,判断为 undefined 或 null
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return Object.prototype.toString.call(value) // 检测其他类型,返回 "[object, tag]" 形式
}
getTag({}) === '[object Object]'
// true
getTag(1) === '[object Number]'
// true
此方法不仅可检测常见的基本类型,还可检测诸如 Date RegExp Arguments 等类型
function bar() {
return arguments
}
getTag(1) === '[object Arguments]'
// true
getTag(new Date()) === '[object Date]'
// true
getTag(/No.1/) === '[object RegExp]'
// true
让我们加大力度,发现除了普通函数,还能检测出是 异步函数 又或是 生成器函数
function fn() {}
function* foo() {}
async function baz() {}
getTag(fn) === '[object Function]'
// true
getTag(foo) === '[object GeneratorFunction]'
// true
getTag(baz) === '[object AsyncFunction]'
// true
到目前为止,Object.prototype.toString.call() 表现得规规矩矩,但是大家发现没有?我们上述的例子都是采用 JS 的内置对象,并且没有修改其内部结构。如果修改了其内部结构就不一定了!
要追究其原理,我们先来细看 ECMAScript® 2020 : 19.1.3.6 Object.prototype.toString ( ) 中相关描述
当调用 toString(O) 方法时,将执行以下步骤:
- 如果
O是undefined,返回"[object Undefined]" - 如果
O是null,返回"[object Null]" - 调用
toObject(O)- 如果
O已是一个对象类型,直接返回O, - 如果是基本数据类型,则对
O进行装箱操作,以布尔值为例,会返回new Boolean(O)
- 如果
- 如果
O是Array,使bulitinTag为"Array" - 如果
O拥有[[ParameterMap]]内部插槽,使bulitinTag为"Arguments" - 如果
O拥有[[Call]]内部插槽,使bulitinTag为"Arguments" - 如果
O拥有[[ErrorData]]内部插槽,使bulitinTag为"Error" - 如果
O拥有[[BooleanData]]内部插槽,使bulitinTag为"Boolean" - 如果
O拥有[[NumberData]]内部插槽,使bulitinTag为"Number" - 如果
O拥有[[StringData]]内部插槽,使bulitinTag为"String" - 如果
O拥有[[DateValue]]内部插槽,使bulitinTag为"Date" - 如果
O拥有[[RegExpMatcher]]内部插槽,使bulitinTag为"RegExp" - 否则,使
bulitinTag为"Object" - 将
tag设置为O的@@toStringTag - 如果
tag不是string,将tag设置为bulitinTag - 返回
"[object, tag]"
这里的内部插糟实现,我理解为对象的内部初始化属性,好比下图中,新建了一个布尔对象,它的
[[PrimitiveValue]]为true对应上述步骤中的[[BooleanData]]
我们重点来看第 14 步,这里有个 @@toStringTag,其实它就是 Symbol.toStringTag 的替代写法,两者是相等的
const m = new Map()
getTag(m) === '[object Map]'
// true
m[Symbol.toStringTag] === 'Map' // 注意:不能使用点操作符去获取 Symbol 属性,会报错
// true
很容易看出,Map 这个 ES6 才出来的数据结构,并没有 bulitinTag,而是通过 @@toStringTag 去获取 tag
相同的还有 Promise
const p = Promise.resolve()
getTag(p) === '[object Promise]'
// true
p[Symbol.toStringTag] === 'Promise'
// true
并且可以人为修改 @@toStringTag,所以使用此方法也不是百分百准确,还是有一定的局限性
const obj = {
[Symbol.toStringTag]: "B2D1"
};
getTag(obj) === '[object B2D1]'
// true
看到这里,相信读者们对 Object.prototype.toString.call() 的原理已经很熟悉了,本文最重要的部分已经结束,不如借着势头,看看其他类型的 toString(),相信能大大夯实读者的 JavaScript 基础
Function.prototype.toString
此方法可以帮助你获得函数的源代码(包括注释),搭配正则可以从中提取出有效的信息
var fnc = function(x) {
// i am comment
return x;
}
fnc.toString()
// "function(x) {
// // i am comment
// return x;
//}"
String.prototype.toString
var x = new String("Hi")
x.toString()
// "Hi"
Boolean.prototype.toString
var yes = new Boolean('yesyes')
var no = new Boolean(null)
yes.toString()
// "true"
no.toString()
// "false"
// 除了假值,此方法都会返回 "true"
// 假值包括 false null undefined +0 -0 '' NaN
Array.prototype.toString
var arr = ['a', 'b']
var x = arr.toString()
var y = arr.join(',')
// 以上两种表达式返回相同内容: 'a,b'
// 小技巧:数组扁平化可以利用 toString()
Number.prototype.toString
只接受一个整形参数 radix(2 <= radix <= 36),默认为 10,表示要转化的进制,返回转化后数字的字符串表示
var count = 10
count.toString() === '10'
(17).toString() === '17'
var x = 6;
x.toString(2) === '110'
(254).toString(16) === 'fe'
