一个小题引发的惨案

106 阅读4分钟

今天逛社区的时候发现了一个题,看了一下感觉还挺有意思的,题目如下:

class Bar {
  constructor() {
    this[Symbol.toPrimitive] = function (hint) {
      switch (hint) {
        case 'number':
          return 3
        case 'string':
          return 'string bar'
        case 'default':
        default:
          return 'default bar'
      }
    }
  }
}
let bar = new Bar()
console.log(3 + bar);
console.log(3 - bar);
console.log(String(bar));

这题看着好像没什么,就是单纯的问打印结果,但是显然并不是这么简单,一般为打印结果的会有很多套头在里面,就比如网上流传的某公司必考的Promise、async执行顺序打印问题,哈哈哈,扯远了。好了我们直接先看一下这个题的答案是什么。

console.log(3 + bar); // 3default bar
console.log(3 - bar); // 0
console.log(String(bar)); //string bar

有点奇怪,为啥会是这个结果?其实出现这个结果的原因有两个:隐式转换 + Symbol.toPrimitive特性。我们来分开谈谈;

隐式转换

首先是隐式转换,这个算是老生常谈的问题了,很多面试官也喜欢问这个问题。我们先把这个题目修改一下:

class Bar {
}
let bar = new Bar()
console.log(3 + bar); //3[object Object]
console.log(3 - bar); // NaN
console.log(String(bar)); //[object Object]

输出结果已经直接给了,我们一个一个看,先看第一个3+bar,js隐式转换中‘+’号有两个意义,一个是字符串连接符,一个数字运算符。这里面bar是一个对象,属于复杂数据类型,且运算符左侧为number类型,所以这个表达式会将number类型和复杂类型统一转换成string类型然后进行相加。我们主要看bar的准换,JavaScript在对复杂类型的隐式转换中会先调用valueOf() 方法,随后调用toString() 方法,所以最后结果是bar转换成'[object,Object]',得出结果:3[object Object]。

image.png

’3 - bar‘ 中同理,不过这里面’-‘代表运算符,JavaScript在遇到减、乘、除的时候会先将非number类型转换为number类型。所以bar转换到string类型是没有办法比较的 ,必需再走一步,将stirng==>number,调用Number() 方法,得到NaN。NaN和任何数运算后都会得到NaN。

image.png

最后一个不用说了,String() 其作用是将对象的值转换为字符串,不过该方法的返回与单个对象的 toString() 返回值相同。

OK,看完了几个结果,我们能大概理出来这三个打印中,bar分别代表什么类型,依次是string、number、string。

Symbol.toPrimitive

接下来看看Symbol.toPrimitive,Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。它的属性如下:

在 Symbol.toPrimitive 属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 "number""string" 和 "default" 中的任意一个。

其具体属性我就不多说了,这里可以看.

console.log(3 + bar): 这里经过上面的操作,我们知道bar会被转换成string类型,但是为什么经过Symbol.toPrimitive判断时候hint会是default呢?其实这里是因为’+‘号的原因,前面我们说过,’+‘号有两个意思,一个是运算操作符 ,一个是字符串连接符 Symbol.toPrimitive并不确定当前对象会转换成什么类型,所以会返回默认的default进行转换。

而另外两个就很好说了,都会有明确的转换类型,所以console.log(3 - bar) 中的bar会被转换成3,结果也变成了0;console.log(String(bar)) 中的bar转换成'string bar',结果也变成了'string bar'。

总结一下

主要记几个隐式转换的规则吧。

1、一个主要小规则,一般情况下,逻辑判断最终需要转换成number类型进行判断,所以对象先转换成字符串,字符串在转换成number,Boolean直接转换成number,0-false、1-true。

image.png

2、‘+’号是比较特殊的,因为其可表运算和连接,所以可以根据以下判断

  • 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
  • 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
  • 当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。

剩下的一些特殊类型个例就不多记录了。