深入浅出JS的隐式类型转换

深入浅出JS的隐式类型转换

最近在重读《JavaScript高级程序设计》一书,读到基本概念一部分,其中罗列了大量的隐式转换规则,如果没有技巧的话,只能死记硬背,效率低下。本人通过自己的总结,得出了一些原理层面上的规律,可以帮助大家快速理解和记忆这些转换规则。

本文主要分为两部分,第一部分是基础知识——类型转换的规则,这部分比较了解的可以直接去看第二部分——理解各种隐式转换场景,第二部分也是本文所要着重表达的核心思想。

类型转换规则

任意类型转为字符串

转换为字符串,我们分两种情况。分别是对象的toString转换法和String构造函数转换法。他们区别和联系如下:

  • 6大数据类型,除了null和undefined,都可以直接调用其toString方法来转为字符串。
  • String构造方法,传入undefined和null时分别返回"undefined"和"null",其他传入值相当于调用其toString()方法。

一句话总结:String方法是toString在功能上的超集,String相比toString增加了对null和undefined的支持。

下面是一些例子。注意,如果你不理解为什么数值、布尔值这些不是对象的值也可以调用toString,请搜索“js包装对象”学习有关知识。

//调用String构造方法实际上就是调用传入参数的toString
let obj = {}
obj.toString = () => 'Modified toString'
obj.toString() //等价于String(obj),返回"Modified toString"

//基本数据类型的转换(包装对象)
let num = 1
num.toString() //等价于String(num),返回"1"
true.toString() //等价于String(true),返回"true"

//null和undefied只能用String构造方法转为字符串
null.toString() //报错
String(null) // "null"

undefined.toString() //报错
String(undefined) //undefined

任意类型转为数值

转为数值的情况与转为字符串类似,不过对应的两个方法是对象的valueOf和Number构造函数。请注意,NaN也是一个数值

  • 6大数据类型,除了null和undefined,都可以直接调用其valueOf方法来尝试转为其本身所代表的原始数据类型,但是相当一部分时候是不会转为数值的,尤其是对象。
  • Number构造方法,一定会把传入的值转化为数值。

Number构造函数对于传入参数的具体操作是:

  • 字符串,忽略前导0,转为数值。转换不成功(出现任何不符合数字形式的字符)则为NaN
  • 若参数是对象,先调用其valueOf方法,如果返回的不是一个数值,那么调用其toString方法,再将这个字符串作为Number的参数传入,得出最终结果。
  • undefined转为NaN,null转为0
  • true转为1,false转为0

PS:ES6的Symbol只能转布尔值和字符串,转其他类型全报错。

let a = {}

//未改写前,valueOf返回对象本身
console.log(a.valueOf()) //Object {  }
console.log(a.toString()) //"[object Object]"
console.log(Number(a)) //NaN,因为toString的结果"[object Object]"无法转为数值

a.toString = () => '2.22'
console.log(a.valueOf()) //Object { toString: toString() }
console.log(a.toString()) //"2.22"
console.log(Number(a)) //2.22,toString的结果"2.22"转为数值是2.22

a.valueOf = () => 3.33
console.log(a.valueOf()) //3.33
console.log(a.toString()) //"2.22"
console.log(Number(a)) //3.33,valueOf返回了数值,所以直接取valueOf的值作为Number的结果

注意,在隐形类型转换为数值时,一般有两种转换模式(本人自己总结的,权威材料无这两个术语)

  1. 强转换,用于只能是数值的场景:必须转为数值,即直接传入Number构造函数,无法转换的会返回NaN
  2. 弱转换,用于可以是数值的场景:对于基本数据类型,传入Number,对于对象,调用其valueOf,如果是数值则返回该数值,如果是其他基本数据类型则传入Number,如果是对象则判定为不能转为数值。

任意类型转为布尔

转为布尔值,我们只需要记忆转为false的那部分。每种数据类型顶多也就一两个,而且都是比较特殊的值,从感性上看这些转为false的值的特点是“空的、不存在的”云云。转为布尔值,要么调用Boolean构造函数,要么使用两个逻辑非运算符,如Boolean(something)!!something

  • 对于数值,NaN和0转为false
  • 对于字符串,空字符串转为false
  • null和undefined都转为false
  • Symbol(ES6)和对象(不算null)全转为true

理解各种隐式转换场景

理解各种场景的基本思路:理解这个场景到底对什么类型有意义,然后其他值都会被隐性的转为这些类型。

加 +

首先,我们要明确,加法只对数值和字符串有意义,分别对应数值计算和字符串拼接的操作,而且数值运算应是加号的首要任务。

所以,js在看到加运算时,需要判定这个操作是数值计算还是字符串拼接,而且优先尝试数值计算,在无法判定为数值计算时判定为字符串拼接。具体流程如下:

  • 首先看是否有字符串,如有任何一方有字符串,那么会判为字符串拼接,会将两方都直接转为字符串。
  • 如果两方都不是字符串,那么首先会对双方都进行弱转换*为数值。如果都成功转为数值,则判定为数值计算,按转换后的结果进行加运算;如果任意一方转换结果不是数值,那么本次运算判定为字符串拼接,双方都转为字符串进行拼接

Note:弱转换的定义请参照第一部分的“任意类型转为数值”小节结尾处。

例子:

//只要有一方为字符串,就判定为字符串拼接
'abc' + {} //"abc[object Object]"
'abc' + true //"abctrue"
'abc' + undefined //"abcundefined"
'abc' + null //"abcnull"
'abc' + 123 //"abc123"

//都不为字符串时,先尝试转为数值,转换成功则用这两个数值做数值计算
let a = Object.assign({}, {
    valueOf(){
        return 1;
    }
})
123 + a //124,因为a的valueOf方法返回1
a + a //2
true + a //2,因为true转为数值是1

//当有一方valueOf无法成功转为数值时,判定为字符串拼接,双方都转为字符串
123 + {} //"123[object Object]"
123 + [1, 2, 3, 4] //"1231,2,3,4"
[] + [] //""

⚠️注意:一个加号和一个减号都可以当作单操作数运算符,对于数值是改变其正负性,对于其他数据类型则强转换为数值,再当作数值来对待。所以说,把一个值强制转为数值其实有三种方法,除了上面提到的两种valueOf和Number,还可以+something

减 -

减法只对数值有意义,所以双方都必须转为Number,直接强转换为数值,出现NaN时计算结果也是NaN
补充一点小知识:NaN任何数值计算都返回NaN,任何比较、相等运算都是false,甚至NaN === NaN也是false

乘*、除/、取模%

这三个运算符也只对数值有意义,所以都必须转为数值。同上。

比较 <、>

比较运算符,对数值和字符串都有意义。对数值来说,就是直接比较数值大小;对字符串来说,是比较其字符的数字编码大小。

实际上,js标准制定者考虑到,就算开发者写出了1 < "6.4"这样的表达式,开发者的意思多半也是判断数值1和数值6.4的大小。所以,js标准给了字符串比较其相当低的优先级:只有两个操作数都为字符串时,才会判定为字符串的比较。

  • 当两个操作数都为字符串,判定为字符串比较,按照从开头开始的每位字符的数字编码逐个比较,比出大小则返回结果,这一位字符相等则比下一位。
  • 当有任意一个操作数不是字符串,则两个操作数都强转换为数值,再进行比较。

例子:

'a' < 'c' //字符a的编码于字符c的,返回true
'D' < 'a' //大写字母编码小于小写字母的,返回true

5.55 < '6.66' //true,右边会被转为数值6.66

//对象a既没重写valueOf也没重写toString,转为数值是NaN,所以不管怎么比都是false
let a = {}
5 > a //false
5 < a //false

//重写toString后,valueOf无法转为数值,会将toString的结果传入Number,所以a在比较中相当于4
a.toString = () => '4'
a < 5 //true
a > 5 //false

//重写valueOf后,valueOf返回了数值,不会再去调toString,所以a在比较中相当于7
a.valueOf = () => 7
a < 5 //false
a > 5 //true

相等 ==

要注意相等(==)和全等(===)的区别!全等不会出现类型转换!

相等运算符与比较运算符类似,也主要用于数值。它有两个特殊情况,一个是两个操作数都是对象时,另一个是null和undefined。其他情况还是转为数值进行比较。

  • null == undefined,而且这两个值在相等判断中绝对不会出现类型转换
  • 对象之间比较引用
  • 其他情况优先转为数值(强转换)
null == undefined //true

let a = {}
let b = {}
let c = a

a == b //false,因为引用的不是同一个对象
a == c //true,引用相同

false == 0
true == 1

let obj = {}
obj == 0 //false
obj.toString = () => '2'
obj ==  2 //true

if、while等需要布尔值的场景

如果传入的是一个表达式,如something < anotherthingthisone == thatone等,按之前提到的进行判断。

如果是传入的一个值,则传入Boolean强制转为布尔值。比较简单就不举例子了

结语

本博客内容均为本人自己总结的经验,如果有什么不对的地方还请批评指正!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值