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

栏目: JavaScript · 发布时间: 6年前

内容简介:前方提醒: 篇幅较长,点个赞或者收藏一下,可以在下一次阅读时方便查找提到js的隐式转换,很多人第一反应都是:坑。的确,对于不熟悉的人来说,

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

提到js的隐式转换,很多人第一反应都是:坑。

的确,对于不熟悉的人来说, js隐式转换 存在着很多的让人无法预测的地方,相信很多人都深受其害,所以,大家在开发过程中,可能会使用 === 来尽量避免隐式转换。但是,为了更加深入的理解 javascript ,本着对知识渴望的精神,我们来通过大量的例子分析分析 js隐式转换 ,熟悉 js隐式转换 的规则,让其在你的眼里变成“显式”。

从一道面试题说起

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

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

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

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

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

但是并没有达到预期,好像触碰到知识盲区了。。。没关系,先放下吧,来看看几个更坑的

[] == ![] // 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隐式转换规则

1. ToString,ToNumber,ToBoolean,ToPrimitive

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

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 的结果。

如果 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。

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


以上所述就是小编给大家介绍的《从一道面试题说起—js隐式转换踩坑合集》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

超级运营术

超级运营术

韩叙 / 中信出版社 / 2017-5

新产品上线,为什么仅仅500次转发能带来300个内测用户? 为什么每一次内容推送,都带来App的一次卸载高峰? 同类活动那么多,怎样做才能超越竞品,占据头条? 为什么有的文案像“小广告”,有的文案像贴心老友? 创业公司与大平台的玩法有何不同? …… 如何从“了解运营”到“精通运营”,可能是运营人*的困惑。《超级运营术》正是对这个问题的全面解答。韩叙总结10年运营......一起来看看 《超级运营术》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试