🚀隐藏在 1 + '1' = '11' 背后的真相
前言
如果问你 1 + '1' = ?,想必你会毫不犹豫脱口而出,答案等于 '11',自信的背后源于这句话:“字符串加任何值都等于字符串”。可是你有没有想过,为什么这个等式得到的是'11'(数字1融入到字符串'1'中),而不是2(字符串'1'融入数字1中)呢?
其实,这和下文要讲的二元操作符有关,可见这些时时刻刻进行着的类型转换,组成了我们代码高速运转的核心。
类型转换
类型转换分为显示类型转换和隐式类型转换,方法也主要分为Number()
、String()
、Boolean()
三种,以及与类型转换涉及极深的操作符们。
Number()
这是es5官方文档中对Number()
的解释:
意思是如果提供了值,则返回由 ToNumber(value)
计算的 Number
值(不是 Number
对象),否则返回 0。
也就是说调用Number
会执行ToNumber
方法,那么再让我们来看看ToNumber
的执行过程:

简单来说就是当传进来以下类型时:
Undefined
:返回NaN
。Null
:返回 0。Boolean
:如果参数为true
,则返回 1。如果参数为false
,则返回 0。Number
:返回结果等于输入参数(无转换)。String
:总结来说就是,只要有非数字就返回NAN
,其他就返回数字(空字符串返回的是0)Object
:会调用ToPrimitive
方法,返回ToPrimitive
得到的返回值。
String()
同样的,我们来看看es5官方文档中对String()
的解释:
这段话说的是返回由 ToString(value)
计算的 String
值(不是 String
对象)。如果未提供 value
,则返回空字符串""
。
同样,调用String
会执行ToString
方法,以下为传入不同类型参数时ToString
的执行过程:

Undefined
:返回Undefined
。Null
:返回Null
。Boolean
:如果参数为true
,则返回"true"
,如果参数为false
,则返回"false"
。Number
:总结来说就是将传入的数字以字符串形式返回,例如 1 返回为String
"0" ,NaN
返回为String
"NaN" 。String
:返回输入参数(无转换)。Object
:会调用ToPrimitive
方法,返回ToPrimitive
得到的返回值。
Boolean()
在这三种类型转换中,Boolean()
类型转换可谓是其中最简单的一种,它会调用ToBoolean
方法:

总结来说就是传入类型为Undefined
和Null
时,返回都为false
。对于Number
数,如果参数为 +0、−0 或 NaN,则结果为 false
,否则,结果为 true
。对于String
,如果参数为空字符串(其长度为零),则结果为 false
,否则,结果为 true
,而区别于上两种方法的是,对于Object
,只要传入的为对象,返回都为true
。
好了,现在你会发现,以上大多都为原始类型之间的转化,而当对象(Object
)转化为数字(Number
)与字符串(String
)时,都会用到一个名为ToPrimitive
的方法,它正是类型转换中至关重要的部分。
ToPrimitive的执行过程
ToPrimitive
,当我们想把对象(Object
)转化为数字(Number
)和字符串(String
)时,ToNumber()
和ToString()
会调用的方法。也就是说ToPrimitive
这个函数的功能就是把引用类型转变成原始类型。
ToPrimitive
方法有两个参数,一个是argument
(类型转换的参数),另一个是type
(要转换的类型),它的执行过程如下:
第一种情况:ToPrimitive(obj, String) ==> String(obj)
- 如果接收到的是原始值,直接返回值。
- 否则,调用
toString
方法,如果得到原始值,返回。 - 否则,调用
valueOf
方法,如果得到原始值,返回。 - 经过上两步都没得到原始值的话,报错。
第二种情况:ToPrimitive(obj, Number) ==> Number(obj)
- 如果接收到的是原始值,直接返回值。
- 否则,调用
valueOf
方法,如果得到原始值,返回。 - 否则,调用
toString
方法,如果得到原始值,返回。 - 经过上两步都没得到原始值的话,报错。
可以看出将对象分别转化为字符串和数字的过程仅有2、3两步的顺序调换。
toString 与 valueOf
所有对象转原始值都会调用 toString 方法, toString方法共有三种:
{}.toString()
返回由"[object 和 class 和 ]“ 组成的字符串[].toString()
返回由数组内部元素以逗号拼接的字符串xx.toString()
返回字符串字面量
valueOf 也可以将对象转化为原始类型,但仅限于包装类对象。

操作符
一元操作符
一元操作符表现为+ xxx
,将+
添加在需要类型转换值的前面,与Number()
执行原理相同。如以下代码:
Number(xxx) == + xxx
console.log(Number(undefined)); // 返回 NaN
console.log(Number(null)); // 返回 0
console.log(+ undefined); // 返回 NaN
console.log(+ null); // 返回 0
二元操作符
v1 + v2 的执行过程如下:
1. lprim = ToPrimitive(v1)
2. rprim = ToPrimitive(v2)
3. 如果 lprim 或者 rprim 是字符串,那么就 ToString(lprim) 或者 ToString(rprim) 再拼接。
4. 否则 ToNumber(lprim) + ToNumber(rprim)
下文的揭秘 1 + '1' = '11' 就是个很好的二元操作符例子。
==
我把官网中关于 == 的判断结果整合为以下表格:
x | y | 判断结果 |
---|---|---|
Undefined | Undefined | true |
Null | Null | true |
Number | NaN | false |
+0 | -0 | true |
-0 | +0 | true |
String | String | true/false (根据内容是否完全相同) |
Boolean | Boolean | true/false (如果为true或false) |
引用同一对象 | 引用同一对象 | true |
null | undefined | true |
undefined | null | true |
Number | String | x == ToNumber(y) |
String | Number | ToNumber(x) == y |
Boolean | / | ToNumber(x) == y |
/ | Boolean | x == ToNumber(y) |
String/Number | Object | x == ToPrimitive(y) |
Object | String/Number | ToPrimitive(x) == y |
同时,在此附上一张Javascript真值表,所有的类型转换都包含在这张矩阵���内。
解答:1 + '1' 为啥 = '11' ?
好了,现在类型转换这块没有人比你更熟了,得出其过程自然也毫不费力。在二元操作符的作用下, 1 + '1' 这个过程被执行为:
v1 + v2(1 + '1'):
1. lprim = ToPrimitive(1) // 为原始类型,返回 1
2. rprim = ToPrimitive('1') // 为原始类型,返回 '1'
3. ToString(1) + '1' //在 lprim 与 rprim 中 rprim 是字符串,于是调用 ToString(1) 再拼接。
4. 1 + '1' = '11' //返回结果等于'11'
这个过程正是“字符串加任何值都等于字符串”的本质。
最后
如果这篇文章能让你对类型转换的理解稍微多一点的话,那它的存在便有了意义。

转载自:https://juejin.cn/post/7388470580464812084