你所不知道的 toString()
November 19, 2019
前言
最近在看 Lodash 的源码,其精简的语法和巧妙的设计,值得大家去细品 。其中有一个工具函数叫 getTag
,旨在获取对象的类型标记(Tag),即我们所熟知的,利用 Object.prototype.toString.call()
去做类型检测。
js
function getTag(value) {// highlight-lineif (value == null) {// 执行非严格相等,判断为 undefined 或 nullreturn value === undefined ? "[object Undefined]" : "[object Null]";}return Object.prototype.toString.call(value); // 检测其他类型,返回 "[object, tag]" 形式}getTag({}) === "[object Object]";// truegetTag(1) === "[object Number]";// true
js
function getTag(value) {// highlight-lineif (value == null) {// 执行非严格相等,判断为 undefined 或 nullreturn value === undefined ? "[object Undefined]" : "[object Null]";}return Object.prototype.toString.call(value); // 检测其他类型,返回 "[object, tag]" 形式}getTag({}) === "[object Object]";// truegetTag(1) === "[object Number]";// true
此方法不仅可检测常见的基本类型,还可检测诸如 Date、RegExp、Arguments 等类型
js
function bar() {return arguments;}getTag(1) === "[object Arguments]";// truegetTag(new Date()) === "[object Date]";// truegetTag(/No.1/) === "[object RegExp]";// true
js
function bar() {return arguments;}getTag(1) === "[object Arguments]";// truegetTag(new Date()) === "[object Date]";// truegetTag(/No.1/) === "[object RegExp]";// true
让我们加大力度,发现除了普通函数,还能检测出是 异步函数 又或是 生成器函数
js
function fn() {}function* foo() {}async function baz() {}getTag(fn) === "[object Function]";// truegetTag(foo) === "[object GeneratorFunction]";// truegetTag(baz) === "[object AsyncFunction]";// true
js
function fn() {}function* foo() {}async function baz() {}getTag(fn) === "[object Function]";// truegetTag(foo) === "[object GeneratorFunction]";// truegetTag(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
的替代写法,两者是相等的
js
const m = new Map();getTag(m) === "[object Map]";// truem[Symbol.toStringTag] === "Map"; // 注意:不能使用点操作符去获取 Symbol 属性,会报错// true
js
const m = new Map();getTag(m) === "[object Map]";// truem[Symbol.toStringTag] === "Map"; // 注意:不能使用点操作符去获取 Symbol 属性,会报错// true
以 ES6 新的数据结构 Map 为例,它并没有 bulitinTag
,而是通过 @@toStringTag
去获取 tag
相同的还有 Promise
js
const p = Promise.resolve();getTag(p) === "[object Promise]";// truep[Symbol.toStringTag] === "Promise";// true
js
const p = Promise.resolve();getTag(p) === "[object Promise]";// truep[Symbol.toStringTag] === "Promise";// true
并且可以人为修改 @@toStringTag
,所以使用此方法也不是百分百准确,还是有一定的局限性
js
const obj = {[Symbol.toStringTag]: "B2D1",};getTag(obj) === "[object B2D1]";// true
js
const obj = {[Symbol.toStringTag]: "B2D1",};getTag(obj) === "[object B2D1]";// true
看到这里,相信读者们对 Object.prototype.toString.call()
的原理已经很熟悉了,本文最重要的部分已经结束,不如借着势头,看看其他类型的 toString()
,相信能大大夯实读者的 JavaScript 基础
Function.prototype.toString
此方法可以帮助你获得函数的源代码(包括注释),搭配正则可以从中提取出有效的信息
js
var fnc = function(x) {// i am commentreturn x;};fnc.toString();// "function(x) {// // i am comment// return x;//}"
js
var fnc = function(x) {// i am commentreturn x;};fnc.toString();// "function(x) {// // i am comment// return x;//}"
String.prototype.toString
js
var x = new String("Hi");x.toString();// "Hi"
js
var x = new String("Hi");x.toString();// "Hi"
Boolean.prototype.toString
js
var yes = new Boolean("yesyes");var no = new Boolean(null);yes.toString();// "true"no.toString();// "false"// 除了假值,此方法都会返回 "true"// 假值包括 false null undefined +0 -0 '' NaN
js
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
js
var arr = ["a", "b"];var x = arr.toString();var y = arr.join(",");// 以上两种表达式返回相同内容: 'a,b'// 小技巧:数组扁平化可以利用 toString()
js
var arr = ["a", "b"];var x = arr.toString();var y = arr.join(",");// 以上两种表达式返回相同内容: 'a,b'// 小技巧:数组扁平化可以利用 toString()
Number.prototype.toString
只接受一个整形参数 radix(2 <= radix <= 36)
,默认为 10
,表示要转化的进制,返回转化后数字的字符串表示
js
var count = 10;(count.toString() === "10"(17).toString()) === "17";var x = 6;(x.toString(2) === "110"(254).toString(16)) === "fe";
js
var count = 10;(count.toString() === "10"(17).toString()) === "17";var x = 6;(x.toString(2) === "110"(254).toString(16)) === "fe";