Symbol公认符号
Symbol的公认符号
经典面试题起手😀
1 |
|
要实现以下效果:
1 |
|
咱们知道 [...arg]
这种展开方法只能对 可迭代对象
使用,然而可迭代对象只包括 Map, Set, Array, WeakMap
,那么如何让一个普通对象变得可迭代呢?这就要用到咱们今天说的公认符号啦☑️
1. 趁热打铁,Symbol.iterator 和 Symbol.asyncIterator😏
这一刻也没有为可迭代对象哀悼,即将赶到战场的是 Symbol.iterator
, 它的作用就是让一个普通对象转变为一个可迭代对象, 让我们来看看它是怎么实现的吧🤩
就比如说咱们最熟悉的 map
方法,当我们 map
的时候,iterator
起到了什么作用呢? 让我们来模拟一个简单的 map
:
1 |
|
1 |
|
现在再看那道面试题,是不是就迎刃而解了😗
1 |
|
这样其实还有点麻烦,可以和生成器结合😏, 由于生成器也是一个特殊的迭代器,所以可以将上面的代码简化为:
1 |
|
当时我看到这个的时候确实被震撼到了,原来还可以这么玩🌟
async版本的iterator功能更强大,可以异步控制迭代器的输出,但是这不是咱们今天的重点,感兴趣的话可以去看看🦏书的第12章末尾,有一个异步可迭代队列的例子
2. 定形数组的爹,Symbol.hasInstance😀
在 ES6 中,当我们使用 instanceof
的时候,会先调用 [Symbol.hasInstance]
这个方法, 如果没有这个方法,则会采用 ES5 的方法,从原型链上查找它的prototype
这就可以自定义instanceof啦!
来看🌰:
1 |
|
3. Symbol.toStringTag
这个就厉害了, 在 ES6
中, toString方法会先调用Symbol.toStringTag
, 如果没有, 那就是经典的 [Object, Object]
1 |
|
4. Symbol.species
这个方法比较冷门,但是确实实用,在 ES6
中, 继承的时候会把上一层的species也继承过来,map
以及 slice
等函数会先调用 new this.constructor[Symbol.species]() 创建新数组
比如:
1 |
|
5. Symbol.isConcatSpreadable🤐
如果是一个数组concat另一个数组,会发生什么?比如 [1]
和 [2]
,是会变成 [1,2]
, 还是 [[1], [2]]
?这就是咱们 isConcatSpreadable 发挥的地方了
1 |
|
简单吧😏
6. 唯一真神,Symbol.toPrimitive🐶🐶🐶🐶🐶🐶🐶🐶
还是经典面试题起手
1 |
|
除了经典做法,还可以使用Symbol.toPrimitive方法,为什么说它是唯一真神,就是因为它可以覆盖 valueOf
和 toString
这两个幻神方法
使用 Symbol.toPrimitive
也很简单, 只不过要注意,与 valueOf
和 toString
不同,它接受一个参数 hint
, hint
有三个取值: default
, string
, number
--"string":
当需要一个字符串表示的对象时,例如通过 String(obj) 进行转换或在某些与字符串相关的操作中(如调用 alert(obj))。
在这种情况下,JavaScript会尝试优先调用对象的 toString 方法(如果存在),然后尝试 valueOf 方法。
-- "number":
当需要一个数字表示的对象时,例如通过 Number(obj) 进行转换、算术运算(如 +obj 或 obj - 0)。
在这种情况下,JavaScript会尝试优先调用对象的 valueOf 方法(如果存在),然后尝试 toString 方法。
-- "default":
当操作不需要特定的类型(既不需要字符串也不需要数字),例如使用双等号 == 进行比较。
在这种情况下,多数情况下的行为与 "number" 类似,但并非总是如此。例如,使用二进制加法操作符 + 时,如果其中一个操作数是对象,那么该对象的 Symbol.toPrimitive 方法(如果存在)会被调用并传入 "default" 作为 hint。
因此,可以将上面的面试题改写成:
1 |
|
这里的写法比较巧妙, 分为外层立即执行函数, 内层(i) => (), 最内层 () => ++i,因此会形成闭包,之后每一次调用都会使返回值递增