前言
以前刚学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牛逼。