隐式类型转换

在这里插入图片描述

前言#

众所周知 javascript 是一种弱类型语言。强类型和弱类型主要是在变量类型处理的角度进行分类的。强类型是一旦指定数据类型,如果不经过强制转换,那么将永远是指定的这个类型。js 中无法声明数据类型,变量类型是根据实际值决定的,由编译器自动调用转换函数进行转换,这种方式称之为隐式转换,今天我们就来聊聊数据类型是如何隐式转换的吧!

小试牛刀#

可能很多小伙伴都知道 JS隐式转换 ,但你是否真的懂它了呢?下面来试试吧!!

定义一个变量 a ,使得下面的表达式结果为 true

a == 1 && a == 2 && a == 3

相信很多小伙伴都懵了,还能这样?别急,我们再来看看其他的!

[] == ![] // true
[] == 0 // true
[2] == 2 // true
['0'] == false // true
+[] // 0
+[1] // 1
+[1, 2] // NaN

这下好了,彻底懵了!!下面让我们来探讨一下呗~~

javascript隐式转换规则#

隐式类型转换是在一定场景下,js 运行环境自动调用这几个方法,尝试转换成期望的数据类型

  • ToString
  • ToNumber
  • ToBoolean
  • ToPrimitive

ToString#

这里所说的 ToString 可不是对象的 toString 方法,而是指其他类型的值转换为字符串类型的操作

  • null:转为 "null"
  • undefined:转为 "undefined"
  • 布尔类型:truefalse 分别被转为 "true""false"
  • 数字类型:转为数字的字符串形式,如 10 转为 "10"1e21 转为 "1e+21"
  • 数组:相当于调用数组的 Array.prototype.join() 方法,如 [1, 2, 3] 转为 "1,2,3",空数组 [] 转为 '' 空字符串,数组中的 nullundefined ,会被当做 '' 空字符串处理
  • 普通对象:相当于直接使用 Object.prototype.toString(),返回 "[object Object]"
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(10) // '10'
String(1e21) // '1e+21'
String([1,2,3]) // '1,2,3' 相当于Array.prototype.join()
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Objecr]' 相当于Object.prototype.toString()

ToNumber#

ToNumber 指其他类型转换为数字类型的操作

null: 转为 0

undefined:转为 NaN

字符串:如果是纯数字形式,则转为对应的数字,空字符转为 0 , 否则一律按转换失败处理,转为 NaN

布尔型:truefalse 被转为 10

数组:数组首先会被转为原始类型,也就是 ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理,关于 ToPrimitive,会在下文中讲到

对象:同数组的处理

Number(null) // 0
Number(undefined) // NaN
Number('10') // 10
Number('10a') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number([]) // 0
Number(['1']) // 1
Number({}) // NaN

ToBoolean#

ToBoolean 指其他类型转换为布尔类型的操作

js 中的假值只有 falsenullundefined''空字符0NaN,其它值转为布尔型都为 true

Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true

ToPrimitive#

ToPrimitive 指对象类型(如:对象、数组)转换为原始类型的操作

  • 当对象类型需要被转为原始类型时,它会先查找对象的 valueOf 方法,如果 valueOf 方法返回原始类型的值,则 ToPrimitive 的结果就是这个值
  • 如果 valueOf 不存在或者 valueOf 方法返回的不是原始类型的值,就会尝试调用对象的 toString 方法,也就是会遵循对象的 ToString 规则,然后使用 toString 的返回值作为 ToPrimitive 的结果

注意:对于不同类型的对象来说,ToPrimitive 的规则有所不同,比如 Date 对象会先调用toString,具体可以参考ECMA标准

如果 valueOftoString 都没有返回原始类型的值,则会抛出异常

Number([]) // 0
Number(['10']) //10
Number(['10', '10']) // NaN
const obj1 = {
valueOf () {
return 100
},
toString () {
return 101
}
}
Number(obj1) // 100
const obj2 = {
toString () {
return 102
}
}
Number(obj2) // 102
const obj3 = {
toString () {
return {}
}
}
Number(obj3) // TypeError

前面说过,对象类型在 ToNumber 时会先 ToPrimitive,再根据转换后的原始类型 ToNumber

  • Number([]), 空数组会先调用 valueOf,但返回的是数组本身,不是原始类型,所以会继续调用 toString,得到空字符串,相当于 Number(''),所以转换后的结果为 "0"
  • 同理,Number(['10']) 相当于 Number('10'),得到结果 10
  • Number(['10', '10']) ,相当于 Number('10,10'),得到结果 NaN
  • obj1valueOf 方法返回原始类型 100,所以 ToPrimitive 的结果为 100
  • obj2 没有 valueOf,但存在 toString,并且返回一个原始类型,所以 Number(obj2) 结果为 102
  • obj3toString 方法返回的不是一个原始类型,无法 ToPrimitive,所以会抛出错误

运算符中的隐式类型转换#

下面只介绍常用的几种:

  • 算术运算符 +
  • 关系运算符 ==

算术运算符 +#

一元 +#

+1 // 1
+1// 1
+-1// -1
+{} // NaN
+[] // 0;
+[1] // 1;
+[1, 2] // NaN

二元 +#

二元加法运算符 + 可以对两个数字做加法,也可以做字符串连接操作,如果其中一个操作数是字符串或者隐式转换为字符串的对象,另外一个操作数将会转换为字符串,加法将进行字符串的连接操作

1 + 2 // 3: 加法
"1" + "2" // "12": 字符串连接
"1" + 2 // "12": 数字转换为字符串后进行字符串连接
1 + + '1' // 2 : 第二个+相当于数学中的正号
1 + {} // "1[object Object]": 对象转换为字符串后进行字符串连接
'1' + true // "1true": 布尔值转换为字符串后进行字符串连接
true + true // 2: 布尔值转换为数字后做加法
2 + null // 2: null转换为0后做加法
2 + undefined // NaN: undefined转换为NaN后做加法
1 + 2 + " blind mice"; // "3 blind mice"
1 +2 + " blind mice"; // "12 blind mice"
//需要注意的是,“++”运算符从不进行字符串连接操作,它总是会将操作数转换为数字并增1。表达式++x并不总和x=x+1完全一样
let x = '1'
++x // 2:

关系运算符 ==#

规则:

  • 对于数字和字符串的抽象比较,将字符串进行 ToNumber 操作后再进行比较
  • 对于布尔值和其他类型的比较,将其布尔类型进行 ToNumber 操作后再进行比较
  • 对于对象和基础类型的比较,将对象进行 ToPrimitive 操作后在进行比较
  • 对象之间的比较,引用同一个对象则为 true,否则为 false

下面来看几个例子:

true == '1' // true
/**
* 布尔类型和其他类型比较适用规则2,true通过ToNumber操作转换为1
* 这时候1 == '1',这时候适用规则1,将'1'通过ToNumber操作转换为1
* 1 == 1 所以输出为true
**/
var obj = {
valueOf: function() { return '1' }
}
true == obj // true
/**
* 首先适用规则2,将true转换为1,此时1 == obj
* 此时适用规则3,将obj转换为'1',此时1 == '1'
* 此时适用规则1,将'1'转换为1,此时1 == 1,所以输出true
**/
[] == ![] // true
/**
* 一般直觉这明细是false,但我们仔细看一下
* ![]先对[]进行强制boolean转换,所以实际上应该是[] == false
* 这样就又回到我们刚刚的规则上了,适用规则2所以[] == 0
* 接着适用规则3,所以 '' == 0
* 最后 ToNumber('') == 0
**/
// 特例
NaN == NaN // false
null == undefined // true,属于ecma规范

最后#

我们看看文章开头说的那道题目的解法:

const a = {
// 定义一个属性来做累加
i: 1,
valueOf () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
// 或者
const a = {
// 定义一个属性来做累加
i: 1,
toString () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true

这样就完美解决啦!!

你掌握了js数据类型隐式转换的方法了吗

参考文章:

https://juejin.cn/post/6844903694039777288