一次印象深刻的隐式转换(js)

125 阅读4分钟

一、简单的隐式转换

console.log({} - {})  //NaN
console.log([] - [])  //0
console.log([] + [1, 2])  //1,2   这里是字符串类型
console.log([] == ![])  //true
console.log({} == {})  //false

😊看似毫无压力,但其实掌握内在的运行逻辑才会知识更牢固

二、包装类

包装类是我们理解隐式转化的基础工具,是理解隐式转化的根基.

Boolean()

Boolean只有两个值: truefalse.

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()很好的补充.

nullundefined的差别是让人诧异的. 但是如果从作者最初设计的规则来看的话,也是合理的: null表示"没有对象",即该处不应该有值。 undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。

console.log(3 < 2 < 1)  //true
console.log(2 < 1 < 1)  //true
  1. 比较 3 < 2 和 2 < 1 的结果, 结果为 false. 此时等式为 false < 1
  2. 比较运算符, 将 false 给 Number 包装类包起来. 得出的结果是 0.
  3. 0 < 1 自然是 true.

引用类型

  const obj = {
    toString() {
      return 222
    },
    valueOf() {
      return 111
    }
  }

Number(obj)时, 返回的结果是111.

此时我修改valueOf() { return {} }的时候.再Number(obj)的话,结果却是222了.

如果我们让valueOf() 返回的是基本类型的话, 那么是和直接使用Number()包裹住valueOf()的值一样,即上面基本类型的结果.

所以我们很容易得出如下这样的一个逻辑过程:

  1. 如果valueOf返回原始值,就Number包装之后返回
  2. 如果valueOf返回的是引用类型,就去toString()方法中找
  3. 如果toString() 返回原始值,就Number包装之后返回
  4. 如果toString()返回的是对象,且是自己重写的.那么就直接报错
  5. 如果不是重写的,那么就调用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
  1. 第一个很简单, 符合string隐式转化的条件, +号两边都有至少一个字符串, 所以全部String() 包裹之后再处理.
  2. +'2' 是一元运算符, 先Number(2). 所以这里变成了 1 + 2 + '2', 那么就是前面两个先进行运算.
  3. 'A' - 'B' 很显然不符合条件.所以触发的是Number()`的隐式转化
  4. 同3.一样

四、总结

console.log([] == ![]) // true

!的运算符比==的高.所以这个代码可以分成三部分. []==,以及![].

  1. 看到 == 这个比较运算法就应该明白 == 两边都要转化成Number类型
  2. 从左到右的话,Number([]), [] 是引用类型,无法直接拿到原始值
  3. valueOf拿不到值,就走Array.prototype.toString.call([]).从上面可以知道, 它返回的是去掉[],即字符串''.
  4. 此时左边为Number(''). 所以左边返回的自然是0.

此时转变成0 == ![]. 接下来转化右边的:

  1. Boolean()一节当中,就可以知道,除了falsey之外,其他都是ture.而此时在!的加持下,[]会进行Boolean()
  2. 此时右边为true!true就为false
  3. Number(false)的结果为0

由此得出 0 == 0, 返回结果为true.

console.log({} == {}) // false
console.log({} != {}) // true
  1. 两边都Number()包裹住.
  2. toString()之后都是[obejct Object]
  3. Number('[obejct Object]') 为NaN
  4. 所以最后转化为console.log(NaN == NaN)的比较

NaN和任何一个值比较都不相等,包括它自己