从一道面试题提及—js隐式转换踩坑合集

前方提醒: 篇幅较长,点个赞或者收藏一下,能够在下一次阅读时方便查找javascript

提到js的隐式转换,不少人第一反应都是:java

的确,对于不熟悉的人来讲,js隐式转换存在着不少的让人没法预测的地方,相信不少人都深受其害,因此,你们在开发过程当中,可能会使用===来尽可能避免隐式转换。可是,为了更加深刻的理解javascript,本着对知识渴望的精神,咱们来经过大量的例子分析分析js隐式转换,熟悉js隐式转换的规则,让其在你的眼里变成“显式”。面试

从一道面试题提及

先来看看一个经典的面试题数组

定义一个变量a,使得下面的表达式结果为trueui

a == 1 && a == 2 && a == 3
复制代码

还有这种操做?先试试看吧,定义a = true?this

var a = true
  a == 1 && a == 2 && a == 3 // false
复制代码

可是并无达到预期,好像触碰到知识盲区了。。。不要紧,先放下吧,来看看几个更坑的spa

[] == ![] // true

  [] == 0 // true
  
  [2] == 2 // true

  ['0'] == false // true

  '0' == false // true

  [] == false // true

  [null] == 0 // true

  null == 0 // false

  [null] == false // true

  null == false // false

  [undefined] == false // true

  undefined == false // false
复制代码

一脸懵逼? 没关系!接下来带你完彻底全的认识javascript的隐式转换prototype

javascript隐式转换规则

1. ToString,ToNumber,ToBoolean,ToPrimitive

咱们须要先了解一下js数据类型之间转换的基本规则,好比数字、字符串、布尔型、数组、对象之间的相互转换。code

1.1 ToString

这里所说的ToString可不是对象的toString方法,而是指其余类型的值转换为字符串类型的操做。对象

这里咱们讨论nullundefined布尔型数字数组普通对象转换为字符串的规则。

  • null:转为"null"
  • undefined:转为"undefined"
  • 布尔类型:truefalse分别被转为"true""false"
  • 数字类型:转为数字的字符串形式,如10转为"10"1e21转为"1e+21"
  • 数组:转为字符串是将全部元素按照","链接起来,至关于调用数组的Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",空数组[]转为空字符串,数组中的nullundefined,会被当作空字符串处理
  • 普通对象:转为字符串至关于直接使用Object.prototype.toString(),返回"[object Object]"
String(null) // 'null'
  String(undefined) // 'undefined'
  String(true) // 'true'
  String(10) // '10'
  String(1e21) // '1e+21'
  String([1,2,3]) // '1,2,3'
  String([]) // ''
  String([null]) // ''
  String([1, undefined, 3]) // '1,,3'
  String({}) // '[object Objecr]'
复制代码

对象的toString方法,知足ToString操做的规则。

注意:上面所说的规则是在默认的状况下,若是修改默认的toString()方法,会致使不一样的结果

1.2 ToNumber

ToNumber指其余类型转换为数字类型的操做。

  • null: 转为0
  • undefined:转为NaN
  • 字符串:若是是纯数字形式,则转为对应的数字,空字符转为0, 不然一概按转换失败处理,转为NaN
  • 布尔型:truefalse被转为10
  • 数组:数组首先会被转为原始类型,也就是ToPrimitive,而后在根据转换后的原始类型按照上面的规则处理,关于ToPrimitive,会在下文中讲到
  • 对象:同数组的处理
Number(null) // 0
  Number(undefined) // NaN
  Number('10') // 10
  Number('10a') // NaN
  Number('') // 0 
  Number(true) // 1
  Number(false) // 0
  Number([]) // 0
  Number(['1']) // 1
  Number({}) // NaN
复制代码

1.3 ToBoolean

ToBoolean指其余类型转换为布尔类型的操做。

js中的假值只有falsenullundefined空字符0NaN,其它值转为布尔型都为true

Boolean(null) // false
  Boolean(undefined) // false
  Boolean('') // flase
  Boolean(NaN) // flase
  Boolean(0) // flase
  Boolean([]) // true
  Boolean({}) // true
  Boolean(Infinity) // true
复制代码

1.4 ToPrimitive

ToPrimitive指对象类型类型(如:对象、数组)转换为原始类型的操做。

  • 当对象类型须要被转为原始类型时,它会先查找对象的valueOf方法,若是valueOf方法返回原始类型的值,则ToPrimitive的结果就是这个值
  • 若是valueOf不存在或者valueOf方法返回的不是原始类型的值,就会尝试调用对象的toString方法,也就是会遵循对象的ToString规则,而后使用toString的返回值做为ToPrimitive的结果。

注意:对于不一样类型的对象来讲,ToPrimitive的规则有所不一样,好比Date对象会先调用toString,具体能够参考ECMA标准

若是valueOftoString都没有返回原始类型的值,则会抛出异常。

Number([]) // 0
  Number(['10']) //10

  const obj1 = {
    valueOf () {
      return 100
    },
    toString () {
      return 101
    }
  }
  Number(obj1) // 100

  const obj2 = {
    toString () {
      return 102
    }
  }
  Number(obj2) // 102

  const obj3 = {
    toString () {
      return {}
    }
  }
  Number(obj3) // TypeError
复制代码

前面说过,对象类型在ToNumber时会先ToPrimitive,再根据转换后的原始类型ToNumber

  • Number([]), 空数组会先调用valueOf,但返回的是数组自己,不是原始类型,因此会继续调用toString,获得空字符串,至关于Number(''),因此转换后的结果为"0"
  • 同理,Number(['10'])至关于Number('10'),获得结果10
  • obj1valueOf方法返回原始类型100,因此ToPrimitive的结果为100
  • obj2没有valueOf,但存在toString,而且返回一个原始类型,因此Number(obj2)结果为102
  • obj3toString方法返回的不是一个原始类型,没法ToPrimitive,因此会抛出错误

看到这里,觉得本身彻底掌握了?别忘了,那道面试题和那一堆让人懵逼的判断还没解决呢,本着对知识渴望的精神,继续往下看吧。

2. 宽松相等(==)比较时的隐式转换规则

宽松相等(==)严格相等(===)的区别在于宽松相等会在比较中进行隐式转换。如今咱们来看看不一样状况下的转换规则。

2.1 布尔类型和其余类型的相等比较

  • 只要布尔类型参与比较,该布尔类型的值首先会被转换为数字类型
  • 根据布尔类型ToNumber规则,true转为1false转为0
false == 0 // true
  true == 1 // true
  true == 2 // false
复制代码

以前有的人可能以为数字2是一个真值,因此true == 2应该为真,如今明白了,布尔类型true参与相等比较会先转为数字1,至关于1 == 2,结果固然是false

咱们平时在使用if判断时,通常都是这样写

const x = 10
  if (x) {
    console.log(x)
  }
复制代码

这里if(x)x会在这里被转换为布尔类型,因此代码能够正常执行。可是若是写成这样:

const x = 10
  if (x == true) {
    console.log(x)
  }
复制代码

代码不会按照预期执行,由于x == true至关于10 == 1

2.2 数字类型和字符串类型的相等比较

  • 数字类型字符串类型作相等比较时,字符串类型会被转换为数字类型
  • 根据字符串的ToNumber规则,若是是纯数字形式的字符串,则转为对应的数字,空字符转为0, 不然一概按转换失败处理,转为NaN
0 == '' // true
  1 == '1' // true
  1e21 == '1e21' // true
  Infinity == 'Infinity' // true
  true == '1' // true
  false == '0' // true
  false == '' // true
复制代码

上面比较的结果和你预期的一致吗? 根据规则,字符串转为数字,布尔型也转为数字,因此结果就显而易见了。

这里就不讨论NaN了,由于NaN和任何值都不相等,包括它本身。

2.3 对象类型和原始类型的相等比较

  • 对象类型原始类型作相等比较时,对象类型会依照ToPrimitive规则转换为原始类型
'[object Object]' == {} // true
  '1,2,3' == [1, 2, 3] // true
复制代码

看一下文章开始时给出的例子

[2] == 2 // true
复制代码

数组[2]是对象类型,因此会进行ToPrimitive操做,也就是先调用valueOf再调用toString,根据数组ToString操做规则,会获得结果"2", 而字符串"2"再和数字2比较时,会先转为数字类型,因此最后获得的结果为true

[null] == 0 // true
  [undefined] == 0 // true
  [] == 0 // true
复制代码

根据上文中提到的数组ToString操做规则,数组元素为nullundefined时,该元素被当作空字符串处理,而空数组[]也被转为空字符串,因此上述代码至关于

'' == 0 // true
  '' == 0 // true
  '' == 0 // true
复制代码

空字符串会转换为数字0,因此结果为true

试试valueOf方法

const a = {
    valueOf () {
      return 10
    }
    toString () {
      return 20
    }
  }
  a == 10 // true
复制代码

对象的ToPrimitive操做会先调用valueOf方法,而且avalueOf方法返回一个原始类型的值,因此ToPrimitive的操做结果就是valueOf方法的返回值10

讲到这里,你是否是想到了最开始的面试题? 对象每次和原始类型作==比较时,都会进行一次ToPrimitive操做,那咱们是否是能够定义一个包含valueOf方法的对象,而后经过某个值的累加来实现?

试一试

const a = {
    // 定义一个属性来作累加
    i: 1,
    valueOf () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true
复制代码

结果正如你所想的,是正确的。固然,当没有定义valueOf方法时,用toString方法也是能够的。

const a = {
    // 定义一个属性来作累加
    i: 1,
    toString () {
      return this.i++
    }
  }
  a == 1 && a == 2 && a == 3 // true
复制代码

2.4 null、undefined和其余类型的比较

  • nullundefined宽松相等的结果为true,这一点你们都知道

其次,nullundefined都是假值,那么

null == false // false
  undefined == false // false
复制代码

竟然跟我想的不同?为何呢? 首先,false转为0,而后呢? 没有而后了,ECMAScript规范中规定nullundefined之间互相宽松相等(==),而且也与其自身相等,但和其余全部的值都不宽松相等(==)

最后

如今再看前面的这一段代码就明了了许多

[] == ![] // true

  [] == 0 // true
  
  [2] == 2 // true

  ['0'] == false // true

  '0' == false // true

  [] == false // true

  [null] == 0 // true

  null == 0 // false

  [null] == false // true

  null == false // false

  [undefined] == false // true

  undefined == false // false
复制代码

最后想告诉你们,不要一味的排斥javascript的隐式转换,应该学会如何去利用它,你的代码中可能存在着不少的隐式转换,只是你忽略了它,要作到知其然,并知其因此然,这样才能有助于咱们深刻的理解javascript。

(看了这么久了,辛苦了,不过我也写了好久啊,点个赞再走吧)

相关文章
相关标签/搜索