【js小知识】[]+ {} =?/{} +[] =?(关于加号的隐式类型转换)

写在前面

今天来聊一聊JS中的加号,就是这个小小的‘+’,你可能会觉得加法不久两种状况,数字和字符串啊。我最开始也是这么认为的,后来真香…(小加号解决了我最近项目的一个小问题放在文末来讲)javascript

每每越是细小的知识点越容易被忽视。越细小的知识越能考验基础比。如{}+{}的结果是什么?[]+{}的结果是什么?html

若是你还知道那就花十分钟康康这篇文章吧。java

js的数据类型

在js中有两类数据类型:web

  • 原始类型:String、Number、Null、Boolean、Undefined、Symbol、Bigint
  • 引用类型:Object(包括数组array和函数function)

特别提醒:MDN文档中js的数据类型有8中,7中数据类型和对象类型,Bigint能够用任意精度表示整数,这篇文章不会使用数组

八种数据类型传送门:MDN文档markdown

加法的一元运算

加法一元运算的内部操做

  • 语法格式: + Expression
  • 说明: 加法的一元运算中,Expression会进行Number(Expression)操做。
    Number是一个将参数转换成数字的方法。

不一样类型参数返回的结果

实践检验

在控制台中输入如下一元运算符,能够看到他们的结果app

结果分析

  • +ace运算分析:

因为ace是一个没有定义的变量,那么应该是undefined类型,因此Tonumber返回的结果是NaN。函数

  • +''运算分析 :

这里加号后面是一个空字符串,空字符串转数字就是0,结果返回0。ui

  • +false的运算分析:

false是一个布尔值,转换成数字就是0。url

  • +'123'和+'qqq'的运算分析:

这两个加号后都是字符串,前者是纯数字,后者是字符,因此前面可以转换成数字,后面的非树转换成NaN。

  • +{}和+[]的分析:

这里加号后面是对象类型,对象类型会先转换成原始值(ToPrimitice方法),而后将这个原始值再转换成数字类型。关于ToPrimitice方法将在后面详细介绍。

ToPrimitive(input,PreferredType?)方法介绍

ToPrimitice是JavaScript引擎内部的一个操做,将参数input转换成原始值。内部执行原理有四部

  1. 若是这个参数自己就是原始值,那么返回自己。

  2. 若是不是参数调用ValueOf方法,valueOf方法返回的结果若是是原始值就返回该原始值。

  3. 若是不是就调用toString方法,toString方法的结果若是是原始值就返回该值。

  4. 若是结果还不是原始值,抛出 TypeError 异常。

tips1

若是valueOf和toString方法不了解,参考NMD官方文档,这里给出连接。valueOf方法官方文档,另外给出toString方法实践的截图

tips2

参数 PreferredType 能够是 Number 或者 String。若是ToPrimitive(input,PreferredType?)中PreferredType 被标志为 String,则转换操做的第二步和第三步的顺序会调换。 若是没有 PreferredType 这个参数,则 PreferredType 的值会按照Number类型上面的四步处理。


是否还记得前面一元运算的实践小案例呢?

+{}会进过ToPrimitive方法转换成原始值后再进行toNumber。最后的结果是NaN,一块儿来看看这个转换的过程吧

//先ToPrimitive({})再Number()
+{}
Number({})
Number({}.valueOf());//结果是{}非原始值 {}.valueOf() isn’t primitive
Number({}.toString())  //结果是字符串,为原始值
Number("[object Object]")//"[object Object]"非数字的字符串
NaN
复制代码

同理,+[]也是同样大的道理

Number([])
Number([].toString())//[].valueOf() isn’t primitive
Number('')
0
复制代码

加法的二元运算

内部原理

有下面这样的一个加法操做。

value1 + value2

在计算这个表达式时,内部的操做步骤是这样的

将两个操做数转换为原始值 (如下是数学表示法的伪代码,不是能够运行的 JavaScript 代码):
prim1 := ToPrimitive(value1) prim2 := ToPrimitive(value2)
PreferredType 被省略,所以 Date 类型的值采用 String,其余类型的值采用 Number。

若是 prim1 或者 prim2 中的任意一个为字符串,则将另一个也转换成字符串,而后返回两个字符串链接操做后的结果。

不然,将 prim1 和 prim2 都转换为数字类型,返回他们的和。

实践检验

咱们来看看文章开头提到的例子,{}+{}、[]+[]这些骚操做。

{}+{}的结果

对于{}+{},加号的两边都是对象类型。先使用ToPrimitive将他们转换成字符串类型。回顾一下ToPrimitive方法内部转换四个过程,应该调用toString将他们转换为两个相同的字符串。相加的结果仍是一个字符串。

咱们来四步分析看看吧

//ToPrimitive({})
1. {}是对象类型非原始值
2. {}.valueOf()f方法返回的结果仍是非原始值
3. {}.toString()方法返回结果是"[object Object]"字符串
4. 是一个原始值
复制代码

因此上面的{}+{}的结果是两个"[object Object]"字符串相加,拼接为的字符串"[Object Object][object Object]"就是最后的结果。

{}+[]的结果

这个{}+[]和上满同样的道理,只不过[].toString返回的结果是是一个空字符,这里为了方便你们,截图展现一下。


可是对于toString和valueOf()方法必定要熟悉,不知道就去查。

因此{}+[]的结果就是"[object Object]"字符串。

可能有的人会对上面的分析表示怀疑,我在终端中输入这两个二元运算,打印的结果和咱们分析的同样,附上结果图:

一个有趣的现象

细心的朋友看到上面的截图可能会有一个疑问。
同样吗?不同啊,{}+[]的结果是0啊,咱们上面的分析不该该是一个字符串吗?这是为何,别急,下面将为你慢慢道来。

缘由:{}+[]是把{}解析为为一个空的代码块而且忽略了它,可能有人问什么是代码块。好比你写的一个箭头函数()=>{},这里面的大括号{}内的内容就是代码块。

在{}+[]中忽略了空的代码块,就变成了了一元运算+[]。因此结果是0。当咱们让数组放在前面,对象放在后面的时候[]+{}就是咱们前面分析的结果。总之不能让{}出如今开头。

拓展

这里再拓展一下,{}.toString()方法报错


这和前面是一个道理,大括号开头的js引擎会认为是代码块,因此会报错。改为({}).toString()就行了。

文章末尾

参考文章

文章的灵感

这篇文章的灵感来源最近在作一个小项目,项目里有一个数据分页功能。分页功能中上一页就是当前页减去1,那么下一页就是当前页加1咯。可是最后报错了。

报错的缘由:当前的页码是从url地址栏中获取的,获取的当前页page是字符串类型。字符串的减法存在隐式类型转换(转换成数字);字符串加上一个数字的结果仍是字符串不是数字

解决错误:转换思路,让当前页-0隐式转换成数字后再加1就能够啦。

经过这个小事儿我就是想说一些很细小的东西每每是你们容易忽略的,每一个知识也都是有他的意义的。

看完的收获

但愿看完这篇文章的你收获了如下知识:

  • valueOf()和toString()方法的运用
  • ToPrimitive(input,PreferredType?)方法的原理
  • 任意数据类型的一元加法运算
  • 任意数据类型的二元加法运算

其中最核心最重要的就是ToPrimitive(input,PreferredType?)方法的原理,再次回顾一下这个原理:这个方法是将某个类型的数据转换成原始类型,他的返回值必定是原始值,若是参数input自己就是原始值就返回这个原始值;

若是不是就调用valueOf方法或者toString方法,至于先调用哪个看PreferredType是number仍是string,默认状况下是number,即先进行valueOf方法看结果是不是原始值,若是不是再使用tosting方法看返回的结果是不是原始值;若是还不是就会报错。

最后,走心文章,但愿可以用心看完,若有错误地方欢迎评论区指正


下篇文章见…