JavaScript类型转换探索:解密`[] == ![]`背后的原理与实践

395 阅读4分钟

JavaScript中的类型转换是一个深刻影响着程序行为的核心机制,它涉及显示(显式)类型转换与隐式类型转换两种方式,深刻影响着比较运算、函数调用等多个方面。本文将深入探讨JavaScript中的类型转换规则,特别关注于显示与隐式转换的差异,并剖析一个经典例子:[] == ![]的结果及背后的原理。

显示类型转换

显示类型转换,即程序员明确指定的类型转换,主要包括三种基本转换方式:

  1. 转布尔值Boolean(x),用于显式将任意值转换为布尔值,遵循JavaScript的真值规则。
let s='s'
let n=123
let f=false
let u=undefined
let nu=null
console.log(Boolean(s));//true
console.log(Boolean(n));//true
console.log(Boolean(Infinity));//true
console.log(Boolean(-Infinity));//true
console.log(Boolean(''));//false
console.log(Boolean(0));//false
console.log(Boolean(-1));//true
console.log(Boolean(NaN));//false
console.log(Boolean(undefined));//false
console.log(Boolean(null));//false
console.log(Boolean(false)); // false
console.log(Boolean());//false
  1. 转数字Number(x),将值转换为数字,无法直接转换的值(如对象、非数字字符串)将转换为NaN
console.log(Number('123'));//123
console.log(Number('abc'));//NaN
console.log(Number(''));//0
console.log(Number('a123'));//NaN
console.log(Number(true));//1
console.log(Number(false));//0
console.log(Number(null));//0
console.log(Number(undefined));//NaN
console.log(Number());//0
  1. 转字符串String(x),将任何值转换为字符串形式。
let num = 123;
let numStr = String(num);
console.log(numStr); // 输出 "123"

隐式类型转换

隐式类型转换通常发生在运算符或函数自动将值转换为所需类型时。

对象转number

先调用ToNumber(x),该函数中会再调用ToPrimitive(x,Number)将对象转为原始值,ToPrimitive(x,Number)会返回一个原始类型。

  • ToPrimitive(object,Number)的原理
  1. 判断接收到的值是不是原始类型,是,则返回
  2. 否则,调用valueOf(),如果得到了原始值,则返回
  3. 否则,调用toString()方法,如果得到了原始值,则返回
  4. 否则,报错
console.log(Number([]));//输出0
// Number([])
//1. ToPrimitive()
//2. let primValue=ToPrimitive([], Number)//''
//3. Number('')

为何输出0?

  • ArrayNumber类型时,先调用ToNumber([]),该函数中会再调用ToPrimitive([],Number)
  • ToPrimitive([],Number)中,首先会判断它是否为原始类型,[]不是原始类型,则调用valueOf(),发现也得不到原始值。那么这时候就会进行调用toString(),将[]转换成了''ToPrimitive([],Number)便完成任务。
  • 最后Number()ToPrimitive([],Number)的返回值进行转换,即Number('')值为0。

对象转String

先调用ToString(x),该函数中会再调用ToPrimitive(x,String)将对象转为原始值

  • ToPrimitive(object,String)
  1. 判断接收到的值是不是原始类型,是,则返回
  2. 否则,调用toString(),如果得到了原始值,则返回
  3. 否则,调用valueOf()方法,如果得到了原始值,则返回
  4. 否则,报错
console.log(String({}));
//ToString({})
//let primValue=ToPrimitive({}, String)//"[object Object]"
//String("[object Object]")//"[object Object]"

为何输出"[object Object]"?

  • ObjectString类型时,先调用ToNumber({}),该函数中会再调用ToPrimitive([],String)
  • ToPrimitive({},Number)中,首先会判断它是否为原始类型,{}不是原始类型,则调用toString()Object有自己的toString()方法,得到"[object Object]"ToPrimitive([],Number)便完成任务。
  • 最后String()ToPrimitive({},String)的返回值进行转换,即String("[object Object]")值为"[object Object]"

布尔值转换

任何对象在需要转换为布尔值时,隐式转换结果总是true

运算符中的转换

如加法运算符+,在操作数不同时会尝试将非数字转换为数字;而比较运算符如==,则会根据两边的类型进行复杂的类型转换以进行比较。

特殊方法:toString()

toString()方法在不同类型的对象上有着不同的表现。

参考上文:JavaScript类型大侦探:一文搞懂typeof、instanceof和toString()那些事儿

一元与二元运算符

console.log(+[])//输出0
  • 一元操作符+ 作为正号使用时,会对操作数进行隐式转换为数字。
console.log(1+'1');//'11'
console.log(1+[]);//1+''
console.log(1+{});//1+'[object Object],'输出'1[object Object]'
console.log({}+[]);//'[object Object]'+'',输出'[object Object]'
  • 二元运算符+ 在字符串与非字符串相加时,会将非字符串转换为字符串;在数字与非数字相加时,会将非数字转换为数字。

=====的区别

  • == 进行宽松相等比较,会根据两边的类型进行必要的隐式类型转换,可能导致意料之外的结果。
  • === 进行严格相等比较,不仅比较值,还比较类型,不进行类型转换,是推荐使用的比较方式。

解析[] == ![]

现在,让我们深入分析[] == ![]这一看似诡异的表达式。首先,解析步骤如下:

  1. ![]:空数组[]在布尔上下文中隐式转换为true,取反后变为false
  2. 然后进行比较[] == false。在此比较中,由于一方是对象,另一方是布尔值,根据==的规则,两边都会被转换为数字进行比较:
    • 根据上文Number([])隐式类型转换示例,得出为0
    • false转换为数字是0
  3. 因此,[]false在转换后都等于0,根据==规则,最终结果为true

通过这个例子,我们不仅理解了JavaScript中的类型转换机制,还见识到了隐式类型转换在某些情况下可能导致的非直观结果。在实际编码中,推荐使用===进行严格比较,以减少因类型转换带来的潜在错误。