「前端每日一问(4)」谈谈 JS 中的类型转换机制

981 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本题难度:⭐ ⭐ ⭐

难度评级为个人主观,最高五星,最低一星。

答:JS 是一种动态类型语言,在声明变量时可以不必指定数据类型,而数据类型会在代码执行时会根据需要自动转换。

常见的类型转换有:

  • 强制转换(显示转换)
  • 自动转换(隐式转换)

显式转换

直接调用下面的 API 转换类型就是显式转换:

  • Boolean()
  • Number()
  • parseInt() 和 parseFloat()
  • String()

Boolean 类型

要转换的数据类型转换为true的值转换为false的值
String非空字符串空字符串
Number非零数值(包括无穷值)0、NaN
Object任意对象null
Undefinedundefined
Boolean('')    // false
Boolean('lin') // true
Boolean('0')   // true   这个要注意一下

Boolean(100)      // true
Boolean(Infinity) // true
Boolean(0)        // false
Boolean(NaN)      // false

Boolean(undefined) // false
Boolean(null)      // false

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
Boolean(function fn() { return false })  // true

Number 类型

要转换的数据类型转换后的值
String可以被解析为数值,则转换为相应的数值,不可以被解析为数值,返回 NaN
Booleantrue 为 1,false 为 0
UndefinedNaN
Null0
Object通常转换成 NaN(除了空数组或只包含单个数值的数组)
Number('100')    // 100
Number('001')    // 1
Number('')       // 0
Number('100qwe') // NaN

Number(false)  // 0
Number(true)   // 1

Number(undefined) // NaN
Number(null)      // 0

Number({})     // NaN 
Number({ age: 18 }) // NaN
Number([])  // 0
Number([1]) // 1
Number([1,2,3]) // NaN
Number(function fn() {})  // NaN

parseInt() 和 parseFloat()

Number转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN

parseInt 相比 Number,就没那么严格了,parseInt 函数逐个解析字符,遇到不能转换的字符就停下来

特别注意parseInt 解析空字符串为 NaN,Number 解析空字符串为0。

parseInt('100qwe') // 100
parseInt('qwe')    // NaN
parseInt('')       // NaN 这个要注意

parseFloat 工作方式和 parseInt 是一样的,只是 parseFloat 可以处理小数。

parseInt(5.433)    // 5

parseFloat(5.433)  // 5.433
parseFloat('5.43.3') // 5.43  parseFloat 只解析第一个小数点

String 类型

要转换的数据类型转换后的值
Number转为数值的字符串
Booleantrue 转为字符串 'true',false 转为字符串'false'
Undefined'undefined'
Null'null'
Object比较复杂,见代码
String(0)      // '0'    这里要注意,不是 ''
String(100)    // '100'
String(001)    // '1'

String(true)  // 'true'
String(false) // 'false'

String(undefined)  // 'undefined'
String(null)       // 'null'

String({})          // '[object Object]'
String({ age: 18 }) // '[object Object]'
String([])          // ''
String([1])         // '1'
String([1,2,3])     // '1,2,3'
String(function fn() {})  // 'function fn() {}'

对象转原始类型

Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。 -- mdn

对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:

  • 如果已经是原始类型了,那就不需要转换了
  • 如果需要转字符串类型就调用 x.toString(),转换为基础类型的话就返回转换的值。不是字符串类型的话就先调用 valueOf,结果不是基础类型的话再调用 toString
  • 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错

相关知识,不懂戳这里 👇

Object.prototype.valueOf()

Object.prototype.toString()

知道这些知识之后,我们来分析一下前文的例子,

Number([1,2,3]) // NaN

[1,2,3].valueOf()  // [1,2,3] 不是基础类型,去调 toString
[1,2,3].toString() // '1,2,3' 
Number('1,2,3' )   // NaN,字符串类型转数字,不可以被解析为数值就返回 NaN
Number({})   // '[object Object]'

const obj = {}
{}.valueOf()             // {},不是基础类型,去调 toString
obj.toString()           // '[object Object]'
Number([object Object])  // NaN

还有很多其他例子,也可以用类似的思路去分析。

隐式转换

下面这些情况,JS 会发生隐式转换,

  • 比较运算(==!=><
  • 算术运算(+-*/%
  • 逻辑语句 ifwhile 需要布尔值的地方

当然,这些场景下运算符两边的操作数要不是同一类型,才会发生隐式转换。

算术运算符

1.减、乘、除

对各种非Number类型运用数学运算符(- * /)时,会先将非Number类型转换为Number类型。

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

JS里 +还可以用来拼接字符串,所以要特殊一些,有下面3条规则:

  • 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
  • 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
  • 当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。

以上 3 点,优先级从高到低

123 + '123' // 123123           (规则1)
123 + null  // 123              (规则2)
123 + true // 124               (规则2)
123 + {}  // 123[object Object] (规则3)

逻辑运算符

if (1) {
    // do something...
}
while (1) {
    // 别这么写,会死循环的
    // do something...
}
  • undefined
  • null
  • false
  • +0
  • -0
  • NaN
  • '' 除了上面几种会被转化成false,其他都换被转化成true

比较运算符

以 == 运算符举例

  • 规则1:NaN 和其他任何类型比较永远返回 false(包括和他自己)。
NaN == NaN // false
  • 规则2:Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
true == 1          // true 
true == '2'        // false, 先把 true 变成 1,再参考规则3
true == ['1']      // true, 先把 true 变成 1, ['1']拆箱成 '1', 再参考规则3
true == ['2']      // false, 同上
undefined == false // false ,首先 false 变成 0,然后参考规则4
null == false      // false,同上
  • 规则3:String 和 Number 比较,先将 String 转换为 Number 类型。
123 == '123' // true, '123' 会先变成 123
'' == 0      // true, '' 会首先变成 0
  • 规则4: null == undefined 比较结果是 true ,除此之外,null、undefined 和其他任何结果的比较值都为 false。
null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false
  • 规则5:原始类型引用类型做比较时,引用类型会依照前文提过的 ToPrimitive 规则转换为原始类型。
'[object Object]' == {} 
// true, 对象和字符串比较,对象通过 toString 得到一个基本类型值
'1,2,3' == [1, 2, 3] 
// true, 同上  [1, 2, 3]通过 toString 得到一个基本类型值
  • 规则6:两个都为引用类型,则比较它们是否指向同一个对象
let obj1 = { name: 'lin' }
let obj2 = { name: 'lin' }
obj1 == obj2    // false

let obj3 = { name: 'lin' }
let obj4 = obj3
obj3 == obj4    // true

其他比较运算符隐式类型转换规则和 == 是一样的,只是转换之后再做其他比较,比如 !=><

结尾

大汇总:

image.png

个人觉得把这张表搞懂,就能明白 JS 中的类型转换机制,至于其他很多稀奇古怪的东西,就别去研究了,以后全面拥抱 TS,这些问题都不是问题。

什么是稀奇古怪的东西?比如,

[] == ! [] // true
[] == []   // false
{} == !{}  // false
{} == {}   // false

又比如: 只用这 6 个字符,就可以写出任意 JavaScript 代码

JS 这门语言确实折磨人,还是 TS 好,没这么多奇奇怪怪的东西。

这是阿林持续更文的第 32 天,输出洞见技术,再会~

参考文章

JavaScript 隐式类型转换,一篇就够了!

上一篇:

「前端每日一问(3)」JS 原始类型和引用类型的区别?

上一篇:

「前端每日一问(5)」typeof null 的结果是什么?为什么?