一、简单的隐式转换
console.log({} - {}) //NaN
console.log([] - []) //0
console.log([] + [1, 2]) //1,2 这里是字符串类型
console.log([] == ![]) //true
console.log({} == {}) //false
😊看似毫无压力,但其实掌握内在的运行逻辑才会知识更牢固
二、包装类
包装类是我们理解隐式转化的基础工具,是理解隐式转化的根基.
Boolean()
Boolean只有两个值: true和false.
Boolean(xxx)为false的类型叫做falsey, 或者是虚值.
包括这些0、null、undefined、false、''、NaN
⚠️有时候把+0和-0算成两个
其他返回的都是true. 叫做truth.
if (Boolean(false)) {
// no execute
}
if (new Boolean(flase)) {
// execute
}
console.log(typeof Boolean(false)) // boolean
console.log(typeof new Boolean(false)) // object
❗️不能说包装了一层包装类的说法,就能够脱离构造函数的相关处理逻辑
Number()
Number()也是有两种类型的结果. 分为两种的处理逻辑. 对于 基本类型的处理, 和对于 引用类型的处理.
基本类型
console.log(Number('1')) // 1
console.log(Number('')) // 0
console.log(Number(NaN)) // NaN
console.log(Number(false)) // 0
console.log(Number(true)) // 1
console.log(Number(null)) // 0
console.log(Number(undefined)) // NaN
//这里稍微绕一点点,往后续看就知道为什么了
console.log(Number({})); //NaN
console.log(typeof new Boolean(2)); //object
console.log(Number(new Boolean(2))); //1
console.log(Number(new Boolean(0))); //0
console.log((new Boolean(2).valueOf())) //true
字符串转数字,注意Number('1ad')为NaN, 一旦字符串中存在非数字的就转化NaN. parseInt()是对 Number()很好的补充.
null和undefined的差别是让人诧异的. 但是如果从作者最初设计的规则来看的话,也是合理的: null表示"没有对象",即该处不应该有值。 undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。
console.log(3 < 2 < 1) //true
console.log(2 < 1 < 1) //true
- 比较 3 < 2 和 2 < 1 的结果, 结果为
false. 此时等式为false < 1 - 比较运算符, 将
false给Number包装类包起来. 得出的结果是0. 0 < 1自然是true.
引用类型
const obj = {
toString() {
return 222
},
valueOf() {
return 111
}
}
当Number(obj)时, 返回的结果是111.
此时我修改valueOf() { return {} }的时候.再Number(obj)的话,结果却是222了.
如果我们让valueOf() 返回的是基本类型的话, 那么是和直接使用Number()包裹住valueOf()的值一样,即上面基本类型的结果.
所以我们很容易得出如下这样的一个逻辑过程:
- 如果
valueOf返回原始值,就Number包装之后返回 - 如果valueOf返回的是引用类型,就去
toString()方法中找 - 如果
toString()返回原始值,就Number包装之后返回 - 如果
toString()返回的是对象,且是自己重写的.那么就直接报错 - 如果不是重写的,那么就调用Obejct.prototye.toString方法
这个也是让a == 1 && a == 2 && a == 2成立的问题的解决方案之一.
String()
const obj = {
toString() {
return 222
},
valueOf() {
return 111
}
}
当我触发String(obj)的时候,就和Number()完全相反.
console.log(String(obj)) //222
直接访问的是toString()方法.
但是如果toString() 返回的是引用类型的话, 就往valueOf()方法上面找. 可以说和Number()的完全相反。
通过重写toString()和valueOf()的方法来了解内部的运行规则是一种很好的方式.
Object.prototype.toString.call()
Object.prototype.toString.call({}) //'[object Object]'
Object.prototype.toString.call([]) //'[object Array]'
Array.prototype.toString.call()
直接把外面的[]给拆了就行.
console.log(String([1])) //'1'
console.log(String([1, 2])) //'1, 2'
console.log(Array.prototype.toString.call([1])) //'1'
console.log(Array.prototype.toString.call([1, 2])) //'1, 2'
console.log(Array.prototype.toString.call([])) //''
注意的点
console.log((123).toString()) // 123
console.log(undefined.toString()) // 报错
console.log(null.toString()) // 报错
undefined和null并没有包装类,它们是基础类型,所以没有toString()方法
三、隐式转化触发规则
boolean的隐式转化触发
if、switch、while、for(;;)、&&、||、!、!!、? : 三元表达式
number的隐式转化触发
运算符:它是用于数字之间的计算的.在JavaScript中也是基本是一样的. + - * % == ~~ & | ~ ^ << <<< 等, 位运算符 、算术运算符
string 的隐式转化触发
+ 且两边大于等于1个string类型. 除了有 symbol类型之外.
console.log(1 + '2' + '2') //'122'
console.log(1 + + '2' + '2') //'32'
console.log('A' - 'B' + '2') //'NaN2'
console.log('A' - 'B' + 2) //NaN
- 第一个很简单, 符合string隐式转化的条件,
+号两边都有至少一个字符串, 所以全部String()包裹之后再处理. +'2'是一元运算符, 先Number(2). 所以这里变成了1 + 2 + '2', 那么就是前面两个先进行运算.- 'A' - 'B'
很显然不符合条件.所以触发的是Number()`的隐式转化 - 同3.一样
四、总结
console.log([] == ![]) // true
!的运算符比==的高.所以这个代码可以分成三部分. []和==,以及![].
- 看到
==这个比较运算法就应该明白==两边都要转化成Number类型 - 从左到右的话,
Number([]),[]是引用类型,无法直接拿到原始值 valueOf拿不到值,就走Array.prototype.toString.call([]).从上面可以知道, 它返回的是去掉[],即字符串''.- 此时左边为
Number(''). 所以左边返回的自然是0.
此时转变成0 == ![]. 接下来转化右边的:
- 在
Boolean()一节当中,就可以知道,除了falsey之外,其他都是ture.而此时在!的加持下,[]会进行Boolean() - 此时右边为
true.!true就为false Number(false)的结果为0
由此得出 0 == 0, 返回结果为true.
console.log({} == {}) // false
console.log({} != {}) // true
- 两边都
Number()包裹住. toString()之后都是[obejct Object]Number('[obejct Object]')为NaN- 所以最后转化为
console.log(NaN == NaN)的比较
NaN和任何一个值比较都不相等,包括它自己