[] == ![] 结果是true?'2'+1等于3还是21?一篇JS类型转换详解

104 阅读4分钟

背景

前些日子做需求的时候遇到了一个bug。
原因是接口获取到的page值是字符串"2",切下一页的时候我直接写page+1去请求,发现根本没变成第3页的正常数据。一排查竟然请求的是第21页的内容。

这个现象引发了我对JS中数据类型隐式转换的好奇。东一榔头西一棒槌的学了不少观点,在此对我备忘录里记的知识点做个整理。内容不少,但看完一定有收获。

显示转换的方式

转换成字符串

.toString() :可以将number,boolean,object转化为字符串,除了null,undefined。

(1).toString() //'1'
true.toString() //'true'
({}).toString() //'[object Object]'
null.toString() //Uncaught TypeError: Cannot read properties of null (reading 'toString')
undefined.toString() //Uncaught TypeError: Cannot read properties of undefined (reading 'toString')

String():功能比toString强大一些

String(undefined) //'undefined'
String(null) //'null'
String(obj) //"[object Object]"

+空字符串

1 + '' //‘1’

转换成布尔值

转换过程中会有5个非true的值,分别是:NaN, 0, null, undefined, ''。
比较反常识的是空数组[]空对象{}都对应布尔值true。

Boolean()

Boolean([]); // true
Boolean({}); // true

!!

!![] //true
!!0 //false
!!NAN //Uncaught ReferenceError: NAN is not defined

转换成数值

Number():空数组得到0,空对象及其他非数字的组成得到NaN

Number([]) // 0
Number({}) // NaN
Number('3') //3
Number('3r') //NAN

parseInt() parseFloat()

parseInt('20',10) //20

一元数学运算符 (- * /)
因为 JS 并没有类型声明,所以任意两个变量或字面量,都可以做加减乘除。

1 - true // 0, 首先把 true 转换为数字 1, 然后执行 1 - 1
1 - null // 1,  首先把 null 转换为数字 0, 然后执行 1 - 0
1 * undefined //  NaN, undefined 转换为数字是 NaN
2 * ['5'] //  10, ['5']首先会变成 '5', 然后再变成数字 5

+ 运算符: 之所以把+单独拎出来,是因为JS里+不仅是个数学运算符,还可以用来拼接字符串。

这时候有三个原则需要遵守:

  1. 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
  2. 一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
  3. 一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。
    以上 3 点,优先级从高到低,即 3+'abc' 会应用规则 1,而 3+true会应用规则2。
[] + 0; // 0
{} + 0; // NaN
123 + '123' // 123123 (规则1)
123 + null  // 123 (规则2)
123 + true // 124 (规则2)
123 + {} // 123[object Object](规则3)

隐式转换的触发

逻辑判断符

使用逻辑判断符时例如:ifwhilefor!都会转换成布尔值。

console.log([] ? true : false) // true
console.log({} ? true : false) // true
!1 // false

空数组[]和空对象{}都是object类型,会被转化为true。

做==比较

虽然我们可以使用 ===避免不必要的状况,不过==里面发生的转换还是很有意思的。
下面五个规则参考 JavaScript 隐式类型转换,一篇就够了

  • 规则 1:NaN和其他任何类型比较永远返回false(包括和他自己)。
NaN == NaN // false
  • 规则 2:Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
true == 1  // true 
true == '2'  // false, 先把 true 变成 1,而不是把 '2' 变成 true
true == ['1']  // true, 先把 true 变成 1, ['1']拆箱成 '1', 再参考规则3
true == ['2']  // false, 同上
undefined == false // false ,首先 false 变成 0,然后参考规则4
null == false // false,同上
  • 规则 3:StringNumber比较,先将String转换为Number类型。
123 == '123' // true, '123' 会先变成 123
'' == 0 // true, '' 会首先变成 0
  • 规则 4:null == undefined比较结果是true,除此之外,nullundefined和其他任何结果的比较值都为false
null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false
  • 规则 5:原始类型引用类型做比较时,引用类型会依照ToPrimitive规则转换为原始类型。

ToPrimitive规则,是引用类型向原始类型转变的规则,它遵循先valueOftoString的模式期望得到一个原始类型。

如果还是没法得到一个原始类型,就会抛出 TypeError

'[object Object]' == {} 
// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值
'1,2,3' == [1, 2, 3] 
// true, 同上  [1, 2, 3]通过 toString 得到一个基本类型值

根据以上规则做两个题

1. [] == ![]

  第一步,![] 会变成 false
  第二步,应用 规则2 ,题目变成: [] == 0
  第三步,应用 规则5 ,[]的valueOf是'',题目变成: '' == 0
  第四步,应用 规则3 ,Number('')是0, 题目变成:0 == 0
  所以, 答案是 true !

2.  [undefined] == false

 第一步,应用 规则5 ,[undefined]通过toString变成 '',题目变成  '' == false
 第二步,应用 规则2 ,题目变成  '' == 0
 第三步,应用 规则3 ,题目变成  0 == 0
 所以, 答案是 true !
 但是!! if([undefined]) 又是个true!

彩蛋(待补充)

上面说过了,在做对象的类型转换的时候,如果死磕源代码,里面会涉及到ToPrimitive,valueOf,toString,那么这里面到底是个什么机制呢?

let obj={abc:'1'}
console.log(obj.valueOf()) //{abc: '1'}
console.log(typeof obj.valueOf()) //object
console.log(typeof obj.toString())//string

(待补充)