很多小伙伴对于Symbol的了解就是ES6新增的基本数据类型,用于创建一个唯一值可以解决key名重复的问题。本文主要探讨几个对于我们有所帮助的几个Symbol函数提供的方法,可以用于啥
Symbol.toStringTag
Symbol.toStringTag这个方法的核心用处就是可以用来检测数据类型,我们都知道检测数据类型最准确的就是Object.prototype.toString.call(),为啥一定要是Object原型上的toString?因为其他类各自实现了属于自己的toString方法按照原型查找机制也就找不到Object原型上的toString方法
对于原型上没有去实现toString的就可以直接通过隐式调用Object.prototype上的toString来返回对应构造函数的name
JSON.toString() // "[object JSON]"
Math.toString() // => "[object Math]"
(new Map()).toString() // => "[object Map]"
(new Set()).toString() // => "[object Set]"
...
以上这些都是类型的原型上没有实现自己的toSring方法所以,可以直接通过原型链找到Objec基类上的toString来通过toStringTag获取到对应构造函数(类)的name
下面通过自定义一个类的toStringTag来看看是不是跟上面说的一样: 调用toSring是通过隐示读取toStringTag来获构造函数的name
let stringTagName = ''
class Beige {
constructor() {
// new.target属性: 用于构造函数之中,返回new命令作用于的那个构造函数
// new.target.name === this.constructor.name
stringTagName = new.target.name
this.constructor
}
// Symbol.toStringTag是内置成员且不允许我们修改
// 所以我们可以通过getter来监听: 当toString读取Symbol.toStringTag的时候来返回自己想返回的数据
get [Symbol.toStringTag]() {
return stringTagName
}
}
let beige = new Beige()
console.log(beige.toString()); // [object Beige]
dir这个类发现我们是没有给原型上去定义toString方法的,所以通过原型链查找机制找到了Object.prototype.toString,该方法内部通过toStringTag来返回了我们定义的Beige(也就是类的名称)
下面我们在原型上加个String方法来验证下Object.prototype.toString会隐示读取类原型上的Symbol.toStringTag
class Beige {
get [Symbol.toStringTag]() {
console.log('你读取了Symbol.toStringTag');
}
}
(new Beige().toString()) // 你读取了Symbol.toStringTag
// 在原型上写个toString方法
class Beige {
get [Symbol.toStringTag]() {
console.log('你读取了Symbol.toStringTag');
}
+ toString() {
+ return '重写String'
+ }
}
(new Beige().toString()) // 没有没有任何输出
扩展new.target
target是ES6给new命令引入的成员属性: 该属性返回了new命令作用于的那个构造函数;这个方法一般用于我们通过写函数的方式来实现类会用到
// 通过new.target来明确该函数是一个构造函数只能通过new来调用
function Beige(name) {
if (new.target !== Beige) {
throw new Error(`${Beige.name}必须使用 new 命令生成实例`)
}
this.name = name
}
let beige = new Beige('a')
Beige.call(beige, 'b') // => 报错
Reflect.construct(beige.constructor, ['b'], Array.constructor) // => 报错
// class的实现方式一定要通过new来生成实例,不然过不了语法校验
class Beige(name) {
}
Beige.call(beige, 'b') // TypeError Class constructor Beige cannot be invoked without 'new'
相反的我们也可以通过new.traget来明确某些方法是个静态方法不允许去通过new来生成实例
function symbol() {
if (new.target) {
throw new Error(`TypeError: ${symbol.name} is not a constructor`)
}
}
let s = new symbol() // symbol is not a constructor
扩展Reflect.construct()
该等同于new target(...args),可以不使用new,来调用构造函数的方法。且
class Beige {
constructor(publicAccounts, ...skill) {
// new.target => Array
this.public = publicAccounts
this.skill = skill
}
}
let publicAccounts = '前端自学驿站'
let skill = ['JS', 'Vue', 'Webpack', 'Node']
let beige = Reflect.construct(
Beige, // 要执行的函数
[publicAccounts, ...skill], // 执行函数的入参必须是类数组Array|argumentsList
Array // 作为新创建对象的原型链对象的constructor() => 实例对象.__proto__.constructor
)
typeof beige // => 'object'
虽然是对象类型,但将Array的prototype作为了实例对象的原型链
beige.skill.push('Babel') // => ['JS', 'Vue', 'Webpack', 'Node', 'Babel']
// 通过Reflect.construct挂载的成员默认不可枚举
beige.push('1') // beige.length === 1
Symbol.toPrimitive
Symbol.toStringTag这个方法的核心用处就是数据类型转换, 比如下面这个面试题
let a = ?
if (a == 1 && a == 2 & a == 3) {
console.log(1)
}
// 怎么样可以让结果输入1???
这种很多方式可以实现: 重写toString/valueOf/gettter监听,重写toString/valueOf是因为"=="隐式转换会调用
这里梳理下隐式转换的条件:
- 当使用
==、&&、||等逻辑操作符进行判断时 - 当使用
+ - * / %四则运算符进行操作时
逻辑操作符操作符隐式转换
// || && 肯定是都将左边转换为Boolean,这大家编程中都常用就不多bb了
// 这里探讨下(==)两等号: 只会在类型不同时进行转换
// 也只有下面两种特殊情况需要特殊记忆,其他情况下都是把类型转换为Number进行比较
null == undefine // => true
NaN == NaN // => false
四则运算符隐式转换
同样都是将类型转换为Number, 只有'+'比较特殊,因为'+'在js中也有字符拼接的作用,所以当类型不同出现'+'甭管是啥肯定都得转换为字符进行拼接
[] + {}
// [] => ''
// {} => "[object Object]"
// => [object Object]
回到正题,我们说了这么多隐式转换,那对于最终转换的类型是怎么来确定的?
console.log(+[1]) // number: 1
console.log(`${{}}`) // string: "[object Object]"
他昨就知道我上面最终是number(1),我为啥不能给他转成string(1)??
let obj = {
name: '北歌',
[Symbol.toPrimitive](hint) {
console.log(hint);
}
}
console.log(obj); // hint => {name: "北歌", Symbol(Symbol.toPrimitive): ƒ}
console.log(+obj); // hint => number
console.log(obj + {}); // hint => default
console.log(`${obj}`); // hint => string
hint参数表示我们在读取这个对象原始值的时候是要转换为那种预期类型
let a = ?
if (a == 1 && a == 2 & a == 3) {
console.log(1)
}
// 回到这道题上来
let a = {
value: 0,
[Symbol.toPrimitive]() {
return ++this.value
}
}
我们通过重写Symbol.toPrimitive,在读取a的原始值的时候就返回了基本类型Number(1),也就不用去调用valueOf|toString
对于Object类型转换基本值具体规则如图下所示
写在最后
如果文章中有那块写的不太好或有问题欢迎大家指出,我也会在后面的文章不停修改。也希望自己进步的同时能跟你们一起成长。喜欢我文章的朋友们也可以关注一下
我会很感激第一批关注我的人。此时,年轻的我和你,轻装上阵;而后,富裕的你和我,满载而归。
往期文章
【前端体系】从一道面试题谈谈对EventLoop的理解(更新了四道进阶题的解析)