给【隐式转换】说拜拜

501 阅读10分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

前言

JS作为弱类型语言的代表,在数据类型方面都相当开放的,但是正因为这样,常常会在开发过程中给我们造成一些不容易找到的Bug。这也成为了可恶面试官难为面试者的利器,对于这种现象,虽然难免让让人生气,但是也不要忘了悄悄学习,实现内卷逆袭。很多朋友都是遇到一道隐式转换的题就记住这道题,没有去学习过内部转换的规则,这样是不能融会贯通的,所以就请从里这开始内卷,理解隐式转换的规则吧。

预热

先来几道简单的题

 null == undefined  //true
 true == 1          //true
 "true" == true     //false
 "true" == false    //fasle
 1 + "1"            //11
 1 - "1"            //0

这些你都对了嘛?这些都是基本类型的转换, 那加入点引用类型的数据,看看又有什么化学反应喃。。

 [] == []            //false
 [] == ![]           //true
 [null] == ![]       //true
 [1,2,3] == "1,2,3"  //true
 ​
 {}+{}               //"[object Object][object Object]"
 {}-{}               //NAN

如果你都答对了,其实就不用看我继续装逼了,当然也是可以继续看的!!

valueOf方法和toString方法

讲类型转换,就必须讲讲这Object.prototype上的卧龙凤雏,因为是Object.prototype上的方法,所以除了Object.create(null)创建的对象,其他所有对象都是有这两个方法的,并且一些内置对象还重写了这对方法。

类型valueOftoString
Array( [1,2,3])数组本身( [1,2,3])以逗号分割的字符串,内部是调用了函数的join的方法( "1,2,3" )
Object({name:"李白"})对象本身( [1,2,3])对象特征的字符串("[object Object]")
Boolean( new Boolean(true) )Boolean值(true)Boolean值的字符串("true")
Function( function(){} )函数本身( function(){} )函数结构的字符串( "function(){}" )
Number( new Number(99) )数值(99)数值的字符串表示( "99" )
String( new String("str") )字符串本身("str")字符串本身("str")
Date(new Date())时间戳(1629354497999时间格式的字符串(" Thu Aug 19 2021 14:28:17 GMT+0800 (中国标准时间) ")

这就有点烧脑经了,这么多怎么记得住呀!!

除了一些内置的对象Array、Number、Boolean、String、Function、Date重写了这对方法,需要记一下。其他对象都是使用默认的方法:valueOf返回对象本身,toString输出对象的类型特征,所以会使用Object.prototype.toString.call()来校验函数的类型。

上面可以看出,一般valueOf都会返回本身或者和本身有关系类型数据,而toString都是输出字符串。

隐式转换的规则

ToPrimitive(obj,PreferredType)

ToPrimitive负责处理引用类型数据转为原始类型数据的类型转换

函数传递两个参数:

  • obj:要转换的数据, 任意类型的数据
  • PreferredType:转换类型的规则,可以是NumberString

obj传入只是要处理数据,不会对函数的运行规则产生影响,所以我们重点记住 PreferredType传入不同的两种值所带来的不同规则吧

  • PreferredType传入值是Number,函数ToPrimitive的执行规则:

    1. obj本身是原始类型,返回obj。
    2. 调用obj.valueOf(),如果结果是原始类型,则返回这个结果。
    3. 调用obj.toString(),如果结果是原始类型,则返回这个结果。
    4. 抛出TypeError异常。
  • PreferredType传入值是String,函数ToPrimitive的执行规则:

    1. input本身是原始类型,返回input。
    2. 调用input.toString(),如果结果是原始类型,则返回这个结果。
    3. 调用input.valueOf(),如果结果是原始类型,则返回这个结果。
    4. 抛出TypeError异常。
  • 不传递PreferredType的值

    1. 判断obj对象的类型
    2. 如果类型是Date,则PreferredType被设置为String
    3. 如果类型不是Date,则PreferredType被设置为Number
    4. 根据PreferredType设置的值,进行相应的规则

在少数情况下发生,当运算符“不确定”期望值的类型时。 例如,二元加法 + 可用于字符串(连接),也可以用于数字(相加),所以字符串和数字这两种类型都可以。因此,当二元加法得到对象类型的参数时,它将依据默认来对其进行转换。 此外,如果对象被用于与字符串、数字或 symbol 进行 == 比较,这时到底应该进行哪种转换也不是很明确,因此使用默认规则。

ToBoolean(data)

ToBoolean负责处理数据转为布尔值的类型转换

函数传递一个参数:

  • data:要转换的数据, 任意类型的数据
参数类型结果
undefinedfalse
nullfalse
Boolean无须转换
Number仅当data参数是 +0, -0, or NaN时,return false;否则return true
String仅当data参数是空字符串时,return false;否则return true; " "不能叫空字符串,因为有空格,所以转换为true
Symboltrue
Objecttrue,所有对象都直接转换为true
ToNumber(data)

ToNumber负责处理数据转为数字的类型转换

函数传递一个参数:

  • data:要转换的数据, 任意类型的数据

根据传入值得不同类型,不同的转换规则。和Number方法对数据的转换规则是一样的

参数类型结果
undefinedNaN
null0
Booleantrue转换1,false转换为+0
Number无须转换
String有字符串解析为数字,例如:‘324’转换为324,‘qwer’转换为NaN; '1a'转换为NAN,和parseInt方法是不一样的
Symbol抛出 TypeError 异常
Object先进行 ToPrimitive(obj, Number)转换得到原始值,在进行ToNumber转换为数字
ToString(data)

ToNumber负责处理数据转为字符串的类型转换

函数传递一个参数:

  • data:要转换的数据, 任意类型的数据

根据传入值得不同类型,不同的转换规则。和String方法对数据的转换规则是一样的

参数类型结果
undefined"undefined"
null"null"
Booleandata为 true, return "true"; data为 false, return "false"
Number用字符串表示数字
String返回argument
Symbol抛出 TypeError 异常
Object先primValue = ToPrimitive(argument, String),再对primValue使用ToString(primValue)

空数组[]转为空字符串,数组中的nullundefined,会被当做空字符串处理

以上四种规则也讲完,隐式类型转换就是在需要那种类型时就使用那种类型的转换规则,然后进行下一步操作。

比如:{}-{}:因为-是数字运算符,所以使用ToNumber把对象转为转为数字、{}&&2:逻辑运算符,肯定要使用ToBoolean把对象转为布尔值

其实一般可以用StringNumberBoolean去模拟隐式类型转换的规则,所以也不用死记,但是要知道这些规则的存在

规则的选择(了解)

一般只要有关操作符或者语句都会需要隐式类型转换,因为有些操作符只能处理某些类型。

其实在日常开发中,其实我们已经了解了那些特定的场景使用特点类型的值,但是我还是要啰嗦一下

规则场景补充
ToPrimit{}==1、{}>1、{}&&1... 所有对象类型转换基础类型{}=={}是不会进行转换的,因为两边都是对象,只会比较对象地址
ToBooleanif语句、for语句循环条件、(&&、、!逻辑运算符)等需要逻辑判断的地方
ToNumber1-‘2’、false-1、0>null、{}-{}、{}-[]等数字运算的地方+ 号运算符属于里面的另类,不会把操作符两边无脑转为数字,后面会介绍转换规则
ToString使用场景是最少,只会出现在一些方法中,document.write、alert

练手

既然知道了规则,也知道了使用场景,那不练练手,对不起看我装这么久的逼!!

  • {}-{}

    1. 因为使用的是- 号运算符,所以两边需要的数字,所以两边要使用ToNumber规则转换

    2. 由于{}是对引用类型,故先进行ToPrimitive(obj, Number)规则转换变为原始类型

      1. 先执行{}的valueOf方法,返回的是{}本身,不是原始类型
      2. 再执行{}的toString方法, 返回的是"[object Object]"的字符串,是原始类型
    3. 再进行ToNumber运算,"[object Object]"就转换为NaN

    4. 运算符两边都进行转换,得到结果NAN-NAN,所以还是NAN

  • ![null]

    1. !需要的数据是布尔值,而[null]显然不是,所以使用ToBoolean转换规则
    2. 由于[]是引用类型,根据所以引用类型转布尔值都是true的原则,所以[null]转为true
    3. !true 自然就是false

"+"号算术运算符独特的转换规则

上面的题是不是让你自信心爆棚,那下面这道喃

  • {} + {}

    按照常规的解答

    1. 因为 +是数字是数字运算符,所以先将两边使用转为ToNumber数字
    2. 和练手第一道题{}-{}一样,最后处理为NAN+NAN,那么结果应该还是NAN了

    但是你打开浏览器控制台试试,结果是"[object Object][object Object]"字符串,是不是很惊讶,为什么不一样,这里就要谈论一下 +号运算符独特的转换规则

来看几个+号参加的表达式,找找规律

 1+ 1    //2
 1+'1'   //11
 +{}     //NAN
 {}+{}   //"[object Object][object Object]"

总结出如下规则:

+号的特殊隐式类型转换.png

再试试 [1]+2这道题

  1. 这里+号运算符是双目运算符

  2. [1] 是引用类型,所以先进行ToPrimitive(obj, ‘ ’)规则转换变为原始类型,PreferredType传入值为空,所以使用默认值,因为[1]不是Date类型对象,则PreferredTyp`被设置为Number,进行相应的规则转换

    1. 先执行[1]的valueOf方法,返回的是[1]本身,不是原始类型
    2. 再执行[1]的toString方法, 返回的是"1"的字符串,是原始类型
  3. "1"+2,因为“1” 是字符串,所以进行字符串拼接,结果值为"12"

现在你就可以试着解释{}+{}等于"[object Object][object Object]"字符串的谜题了!!!

"=="运算符独特的转换规则

学到这里差不多就可以解决90%的面试题的,但是还有一个重点知识,==的隐式类型转换

老规矩从题开始:

 null    ==  undefined  //true
 "true"  ==  true       //false
 [null]  ==  ![]        //true
 [1,2,3] ==  "1,2,3"    //true

能完全答对嘛?不能就记一下下面的规则吧!!

你所忽略的js隐式转换这篇文章梳理的很完整,我就不赘叙了,来画蛇填点足吧!

未命名文件 (5).png 在线链接

  • NAN不等于任何值,包括本身

     NAN==NAN  //false
    
  • 当x或y为null、undefined两者中一个,那么只有以下情况才能返回true

     null==null             //true
     undefined==undefined   //true
     undefined==null        //true
    
  • 当type(a)和type(b) 类型不相等的时候:有引用类型时,引用类型会使用ToPrimitive(obj, ‘ ’)规则转换变为原始类型再进行比较操作

     "1"==1     //true   "1"会进行toNumber规则
    
  • 当type(a)和type(b) 类型不相等的时候:有String类型时,String转化为Number类型比较

     "1"==1     //true   "1"会进行toNumber规则
    
  • 当type(a)和type(b) 类型不相等的时候:有Boolean类型时,Boolean转化为Number类型比较。

     true==1     //true   true会进行toNumber规则
    

参考文章

你所忽略的js隐式转换

你掌握了吗?——js数据类型隐式转换