js 隐式类型转换

287 阅读4分钟

前言

以前刚学js的时候,总觉得js是种弟弟语言,这种类型可以那样用,那种类型可以这样用,仿佛你能想到的操作,都被js给容错处理了。根本不知道它底层到底是怎么操作的,也就不知道每一步该怎么写更好,对于我这个强迫症来说,不知道自己的操作是不是最优的,或者说有没有问题,心里简直跟猫抓一样。

常跟朋友说不喜欢弱类型语言,感觉没有强类型那么严密,写C的时候,整型数组里面只能是整数,int类型只能与int类型运算,float类型只能与float类型运算,每一步执行的操作都知道其底层原理干了什么(大概),但是js中

a = 100 + '100' // 100100
b = 100 - '100' // 0
c = 100 / '100' // 1
d = {valueOf: () => {return 10}} / 1 // 10
e = {toString: () => {return 'hello'}} + '1' // 'hello1'
f = [1, 'sdf'] + 1 // "1,sdf1"
g = 100 * '2' // 200

fuck,这底层到底干了啥,

弄清楚隐式类型转换能让我们写代码更加得心应手。 建议大家先看原理,看完后,再结合现象回顾原理,如此往返多次,应该就能彻底理解它了(不懂留言交流哈)。

原理

当js进行任何操作时如果期望的类型与传入的类型不同时,会进行隐形的类型转换, 具体怎么转换看它期望的类型。 ECMAScript规范

这里列举4种常见的隐式内部操作,ToBoolean,ToNumber,ToString,ToPrimitive。

执行运算符的时候会把传入类型转换成相应需要类型(已经是对应类型时不需要转换):

+,-, * ,/

函数同理:

parseInt,等

1、ToBoolean

根据相应的参数返回如下

2、ToNumber

根据参数

非对象:相应处理

对象:调用ToPrimitive,传入暗示数值

3、ToString

根据参数

非对象:相应处理

对象:调用ToPrimitive,传入暗示字符串

注意,传入数值时,即表中见918,如下,ECMA连接

4、ToPrimitive

转换成基本值,不理解先往下看。

调用ToPrimitive内部操作时,如果传入基本值(未定义,空值,布尔值,数值,字符串),不会进行任何操作,直接返回,如果传入的是对象,调用对象的 [[DefaultValue]] 内部方法(把接收到的暗示类型传进去)。

!!!注意,[[DefaultValue]]操作如下,ECMA连接,此处是重点,对象执行隐式转换的时候是调用valueOf还是toString,都是这一步的操作!

现象

非对象类型的隐式转换,根据相应的操作,执行相应的转换,每一个表达式要做哪些转换可以在这里查找,此处不再赘述。

此处主要列举对象的隐式转换


var obj1 = {
     valueOf: () => {
         return '100'
     },
     toString: () => {
         return '200'
     }
 }

var obj2 = {
     valueOf: () => {
         return {}
     },
     toString: () => {
         return '200'
     }
 }
 
var obj3 = {
     valueOf: () => {
         return '100'
     },
     toString: () => {
         return {}
     }
 }
 
var obj4 = {
     valueOf: () => {
         return {}
     },
     toString: () => {
         return {}
     }
 }
 
/***************操作符的隐式类型转换*********************/
/* 
* 此处一元加法运算符,期望传入Number,但是传入的是对象,隐式执行ToNumber,
* ToNumber 检查操作数,是对象执行 ToPrimitive(暗示数值) 操作,
* ToPrimitive(暗示数值)操作先执行valueOf,返回的是基本类型数据'100',
* 再次执行ToNumber,得到100,然后执行一元 +
*/
console.log(+obj1) // 100

/* 
* 此处一元加法运算符,期望传入Number,但是传入的是对象,隐式执行ToNumber,
* ToNumber 检查操作数,是对象执行 ToPrimitive(暗示数值) 操作,
* ToPrimitive(暗示数值)操作先执行valueOf,返回的是对象,再执行toString,返回的是基本类型数据'200'
* 再次执行ToNumber,得到200,然后执行一元 +
*/
console.log(+obj2) // 200

/* 
* 此处一元加法运算符,期望传入Number,但是传入的是对象,隐式执行ToNumber,
* ToNumber 检查操作数,是对象执行 ToPrimitive(暗示数值) 操作,
* ToPrimitive(暗示数值)操作先执行valueOf,返回的是基本类型数据'100',
* 再次执行ToNumber,得到100,然后执行一元 +
*/
console.log(+obj3) // 100

/* 
* 此处一元加法运算符,期望传入Number,但是传入的是对象,隐式执行ToNumber,
* ToNumber 检查操作数,是对象执行 ToPrimitive(暗示数值) 操作,
* ToPrimitive(暗示数值)操作先执行valueOf,返回的是对象,再执行toString,
* toString 返回的也是对象,抛出TypeError
*/
console.log(+obj4) // TypeError

其他转换都是根据上面的操作流程进行的

需要注意的是先执行valueOf,还是toString

  • 暗示数值的时候先执行valueOf,如果返回的不是基本类型,则再调用toString,如果返回的还不是基本类型,则抛出TypeError

  • 暗示字符串的时候先执行toString,如果返回的不是基本类型,则再调用valueOf,如果返回的还不是基本类型,则抛出TypeError

  • 当没有暗示时,默认暗示Number


var obj4 = {
     valueOf: () => {
        console.log('valueOf')
        return {}
     },
     toString: () => {
         console.log('toString')
         return {}
     }
 }

总结

比如parseInt(),这个函数期望传入String类型,当我们传入对象时,会和前面 String(obj) 一样先进行转换,js所有操作都会执行这样的类型转换,js牛逼。