这是我参与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)创建的对象,其他所有对象都是有这两个方法的,并且一些内置对象还重写了这对方法。
| 类型 | valueOf | toString |
|---|---|---|
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:转换类型的规则,可以是Number或String
obj传入只是要处理数据,不会对函数的运行规则产生影响,所以我们重点记住 PreferredType传入不同的两种值所带来的不同规则吧
-
PreferredType传入值是Number,函数ToPrimitive的执行规则:- obj本身是原始类型,返回obj。
- 调用obj.valueOf(),如果结果是原始类型,则返回这个结果。
- 调用obj.toString(),如果结果是原始类型,则返回这个结果。
- 抛出TypeError异常。
-
PreferredType传入值是String,函数ToPrimitive的执行规则:- input本身是原始类型,返回input。
- 调用input.toString(),如果结果是原始类型,则返回这个结果。
- 调用input.valueOf(),如果结果是原始类型,则返回这个结果。
- 抛出TypeError异常。
-
不传递
PreferredType的值- 判断
obj对象的类型 - 如果类型是
Date,则PreferredType被设置为String - 如果类型不是
Date,则PreferredType被设置为Number - 根据
PreferredType设置的值,进行相应的规则
- 判断
在少数情况下发生,当运算符“不确定”期望值的类型时。 例如,二元加法 + 可用于字符串(连接),也可以用于数字(相加),所以字符串和数字这两种类型都可以。因此,当二元加法得到对象类型的参数时,它将依据默认来对其进行转换。 此外,如果对象被用于与字符串、数字或 symbol 进行 == 比较,这时到底应该进行哪种转换也不是很明确,因此使用默认规则。
ToBoolean(data)
ToBoolean负责处理数据转为布尔值的类型转换
函数传递一个参数:
data:要转换的数据, 任意类型的数据
| 参数类型 | 结果 |
|---|---|
| undefined | false |
| null | false |
| Boolean | 无须转换 |
| Number | 仅当data参数是 +0, -0, or NaN时,return false;否则return true |
| String | 仅当data参数是空字符串时,return false;否则return true; " "不能叫空字符串,因为有空格,所以转换为true |
| Symbol | true |
| Object | true,所有对象都直接转换为true |
ToNumber(data)
ToNumber负责处理数据转为数字的类型转换
函数传递一个参数:
data:要转换的数据, 任意类型的数据
根据传入值得不同类型,不同的转换规则。和Number方法对数据的转换规则是一样的
| 参数类型 | 结果 |
|---|---|
| undefined | NaN |
| null | 0 |
| Boolean | true转换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" |
| Boolean | data为 true, return "true"; data为 false, return "false" |
| Number | 用字符串表示数字 |
| String | 返回argument |
| Symbol | 抛出 TypeError 异常 |
| Object | 先primValue = ToPrimitive(argument, String),再对primValue使用ToString(primValue) |
空数组[]转为空字符串,数组中的null或undefined,会被当做空字符串处理
以上四种规则也讲完,隐式类型转换就是在需要那种类型时就使用那种类型的转换规则,然后进行下一步操作。
比如:{}-{}:因为-是数字运算符,所以使用ToNumber把对象转为转为数字、{}&&2:逻辑运算符,肯定要使用ToBoolean把对象转为布尔值
其实一般可以用String、Number、Boolean去模拟隐式类型转换的规则,所以也不用死记,但是要知道这些规则的存在
规则的选择(了解)
一般只要有关操作符或者语句都会需要隐式类型转换,因为有些操作符只能处理某些类型。
其实在日常开发中,其实我们已经了解了那些特定的场景使用特点类型的值,但是我还是要啰嗦一下
| 规则 | 场景 | 补充 | ||
|---|---|---|---|---|
| ToPrimit | {}==1、{}>1、{}&&1... 所有对象类型转换基础类型 | {}=={}是不会进行转换的,因为两边都是对象,只会比较对象地址 | ||
| ToBoolean | if语句、for语句循环条件、(&&、 | 、!逻辑运算符)等需要逻辑判断的地方 | ||
| ToNumber | 1-‘2’、false-1、0>null、{}-{}、{}-[]等数字运算的地方 | + 号运算符属于里面的另类,不会把操作符两边无脑转为数字,后面会介绍转换规则 | ||
| ToString | 使用场景是最少,只会出现在一些方法中,document.write、alert |
练手
既然知道了规则,也知道了使用场景,那不练练手,对不起看我装这么久的逼!!
-
{}-{}-
因为使用的是
-号运算符,所以两边需要的数字,所以两边要使用ToNumber规则转换 -
由于{}是对引用类型,故先进行ToPrimitive(obj, Number)规则转换变为原始类型
- 先执行{}的valueOf方法,返回的是{}本身,不是原始类型
- 再执行{}的toString方法, 返回的是"[object Object]"的字符串,是原始类型
-
再进行ToNumber运算,"[object Object]"就转换为NaN
-
运算符两边都进行转换,得到结果NAN-NAN,所以还是NAN
-
-
![null]!需要的数据是布尔值,而[null]显然不是,所以使用ToBoolean转换规则- 由于[]是引用类型,根据所以引用类型转布尔值都是true的原则,所以[null]转为true
- !true 自然就是false
"+"号算术运算符独特的转换规则
上面的题是不是让你自信心爆棚,那下面这道喃
-
{} + {}按照常规的解答
- 因为
+是数字是数字运算符,所以先将两边使用转为ToNumber数字 - 和练手第一道题
{}-{}一样,最后处理为NAN+NAN,那么结果应该还是NAN了
但是你打开浏览器控制台试试,结果是
"[object Object][object Object]"字符串,是不是很惊讶,为什么不一样,这里就要谈论一下+号运算符独特的转换规则 - 因为
来看几个+号参加的表达式,找找规律
1+ 1 //2
1+'1' //11
+{} //NAN
{}+{} //"[object Object][object Object]"
总结出如下规则:
再试试 [1]+2这道题
-
这里
+号运算符是双目运算符 -
[1]是引用类型,所以先进行ToPrimitive(obj, ‘ ’)规则转换变为原始类型,PreferredType传入值为空,所以使用默认值,因为[1]不是Date类型对象,则PreferredTyp`被设置为Number,进行相应的规则转换- 先执行[1]的valueOf方法,返回的是[1]本身,不是原始类型
- 再执行[1]的toString方法, 返回的是"1"的字符串,是原始类型
-
"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隐式转换这篇文章梳理的很完整,我就不赘叙了,来画蛇填点足吧!
-
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规则