JS的{} + {}与{} + []的效果是什么?

在JS中的运算符合营的状况中,(+)标记是很罕见的一种,它有以下的运用状况:

  • 数字的加法运算,二元运算

  • 字符串的衔接运算,二元运算,最高优先

  • 正号,一元运算,可延长为强迫转换其他范例的运算元为数字范例

固然,假如斟酌多个标记一同运用时,(+=)与(++)又是别的的用处。

另一个罕见的是花括号({}),它有两个用处也很罕见:

  • 对象的字面笔墨定义

  • 区块语句

所以,要能回复这个题目,要先搞清楚重点是什么?

第一个重点是:

加号(+)运算在JS中在运用上的划定是什么。

第二个重点则是:

对象在JS中是怎样转换为原始数据范例的值的。

加号运算符(+)

除了上面申明的罕见状况外,在规范中转换的划定规矩另有以下几个,要注重它的递次:

operand + operand = result

  1. 运用ToPrimitive运算转换左与右运算元为原始数据范例值(primitive)

  2. 在第1步转换后,假如有运算元涌现原始数据范例是”字符串”范例值时,则另一运算元作强迫转换为字符串,然后作字符串的衔接运算(concatenation)

  3. 在其他状况时,一切运算元都邑转换为原始数据范例的”数字”范例值,然后作数学的相加运算(addition)

ToPrimitive内部运算

因而,加号运算符只能运用于原始数据范例,那末关于对象范例的值,要怎样转换为原始数据范例?下面申明是怎样转换为原始数据范例的。

ECMAScript 6th Edition #7.1.1,有一个笼统的ToPrimitive运算,它会用于对象转换为原始数据范例,这个运算不只会用在加号运算符,也会用在关联比较或值相称比较的运算中。下面有关于ToPrimitive的申明语法:

ToPrimitive(input, PreferredType?)

input代表代入的值,而PreferredType可所以数字(Number)或字符串(String)个中一种,这会代表”优先的”、”首选的”的要举行转换到哪种原始范例,转换的步骤会依这里的值而有所差别。但假如没有供应这个值也就是预设状况,则会设置转换的hint值为"default"。这个首选的转换原始范例的指导(hint值),是在作内部转换时由JS视状况自动加上的,平常状况就是预设值。

而在JS的Object原型的设想中,都肯定会有两个valueOftoString要领,所以这两个要领在一切对象内里都邑有,不过它们在转换有能够会交流被挪用的递次。

当PreferredType为数字(Number)时

PreferredType为数字(Number)时,input为要被转换的值,以下是转换这个input值的步骤:

  1. 假如input是原始数据范例,则直接返回input

  2. 不然,假如input是个对象时,则挪用对象的valueOf()要领,假如能获得原始数据范例的值,则返回这个值。

  3. 不然,假如input是个对象时,挪用对象的toString()要领,假如能获得原始数据范例的值,则返回这个值。

  4. 不然,抛出TypeError毛病。

当PreferredType为字符串(String)时

上面的步骤2与3对换,犹以下面所说:

  1. 假如input是原始数据范例,则直接返回input

  2. 不然,假如input是个对象时,挪用对象的toString()要领,假如能获得原始数据范例的值,则返回这个值。

  3. 不然,假如input是个对象时,则挪用对象的valueOf()要领,假如能获得原始数据范例的值,则返回这个值。

  4. 不然,抛出TypeError毛病。

PreferredType没供应时,也就是hint为”default”时

PreferredType为数字(Number)时的步骤雷同。

数字实际上是预设的首选范例,也就是说在平常状况下,加号运算中的对象要作转型时,都是先挪用valueOf再挪用toString

但这有两个非常,一个是Date对象,另一是Symbol对象,它们掩盖了本来的PreferredType行动,Date对象的预设首选范例是字符串(String)。

因而你会看到在一些教程文件上会区分为两大类对象,一类是 Date 对象,另一类叫 非Date(non-date) 对象。由于这两大类的对象在举行转换为原始数据范例时,首选范例恰好相反。

模仿代码申明

以简朴的模仿代码来申明,加号运算符(+)的运转历程就是像下面这个模仿码一样,我想这会很轻易邃晓:

a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)

    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))

步骤简朴来讲就是,运算元都用ToPrimitive先转换为原始数据范例,然后其一是字符串时,运用ToString强迫转换另一个运算元,然后作字符串衔接运算。要不然,就是都运用ToNumber强迫转换为数字作加法运算。

ToPrimitive在碰到对象范例时,预设挪用体式格局是先挪用valueOf再挪用toString,平常状况数字范例是首选范例。

上面说的ToStringToNumber这两个也是JS内部的笼统运算。

valueOf与toString要领

valueOfToString是在Object中的两个必有的要领,位于Object.prototype上,它是对象要转为原始数据范例的两个姐妹要领。从上面的内容已能够看到,ToPrimitive这个笼统的内部运算,会遵照设置的首选的范例,决议要前后挪用valueOftoString要领的递次,当数字为首选范例时,优先运用valueOf,然后再挪用toString。当字符串为首选范例时,则是相反的递次。预设挪用体式格局则是如数字首选范例一样,是先挪用valueOf再挪用toString

JS关于Object与Array的设想

在JS中所设想的Object纯对象范例的valueOftoString要领,它们的返回以下:

  • valueOf要领返回值: 对象自身。

  • toString要领返回值: “[object Object]”字符串值,差别的内建对象的返回值是”[object type]”字符串,”type”指的是对象自身的范例辨认,比方Math对象是返回”[object Math]”字符串。但有些内建对象由于掩盖了这个要领,所以直接挪用时不是这类值。(注重: 这个返回字符串的前面的”object”开首英文是小写,背面开首英文是大写)

你有能够会看过,应用Object中的toString来举行种种差别对象的推断语法,这在之前JS能用的函数库或要领不多的年代常常看到,不过它须要合营运用函数中的call要领,才输出准确的对象范例值,比方:

> Object.prototype.toString.call([])
"[object Array]"

> Object.prototype.toString.call(new Date)
"[object Date]"

所以,从上面的内容就能够晓得,下面的这段代码的效果会是挪用到toString要领(由于valueOf要领的返回并非原始的数据范例):

> 1 + {}
"1[object Object]"

一元正号(+),具有让首选范例(也就是hint)设置为数字(Number)的功用,所以能够强迫让对象转为数字范例,平常的对象会转为:

> +{} //相当于 +"[object Object]"
NaN

固然,对象的这两个要领都能够被掩盖,你能够用下面的代码来视察这两个要领的运转递次,下面这个都是先挪用valueOf的状况:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return 'obj'; // string
  }
}
console.log(1 + obj);  //valueOf -> toString -> '1obj'
console.log(+obj); //valueOf -> toString -> NaN
console.log('' + obj); //valueOf -> toString -> 'obj'

先挪用toString的状况比较少见,也许只要Date对象或强迫要转换为字符串时才会看到:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return 1; // number
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}
alert(obj); //toString -> valueOf -> alert("1");
String(obj); //toString -> valueOf -> "1";

而下面这个例子会形成毛病,由于不论递次是怎样都得不到原始数据范例的值,毛病音讯是”TypeError: Cannot convert object to primitive value”,从这个音讯中很邃晓的通知你,它这内里会须要转换对象到原始数据范例:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}

console.log(obj + obj);  //valueOf -> toString -> error!

Array(数组)很常用到,虽然它是个对象范例,但它与Object的设想差别,它的toString有掩盖,申明一下数组的valueOftoString的两个要领的返回值:

  • valueOf要领返回值: 对象自身。(与Object一样)

  • toString要领返回值: 相当于用数组值挪用join(',')所返回的字符串。也就是[1,2,3].toString()会是"1,2,3",这点要迥殊注重。

Function对象很少会用到,它的toString也有被掩盖,所以并非Object中的谁人toString,Function对象的valueOftoString的两个要领的返回值:

  • valueOf要领返回值: 对象自身。(与Object一样)

  • toString要领返回值: 函数中包括的代码转为字符串值

Number、String、Boolean三个包装对象

包装对象是JS为原始数据范例数字、字符串、布尔特地设想的对象,一切的这三种原始数据范例所运用到的属性与要领,都是在这上面所供应。

包装对象的valueOftoString的两个要领在原型上有经由掩盖,所以它们的返回值与平常的Object的设想差别:

  • valueOf要领返回值: 对应的原始数据范例值

  • toString要领返回值: 对应的原始数据范例值,转换为字符串范例时的字符串值

toString要领会比较迥殊,这三个包装对象里的toString的细部申明以下:

  • Number包装对象的toString要领: 能够有一个传参,能够决议转换为字符串时的进位(2、8、16)

  • String包装对象的toString要领: 与String包装对象中的valueOf雷同返回效果

  • Boolean包装对象的toString要领: 返回”true”或”false”字符串

别的,常被搞混的是直接运用Number()String()Boolean()三个强迫转换函数的用法,这与包装对象的用法差别,包装对象是必需运用new关键字举行对象实例化的,比方new Number(123),而Number('123')则是强迫转换其他范例为数字范例的函数。

Number()String()Boolean()三个强迫转换函数,所对应的就是在ECMAScript规范中的ToNumberToStringToBoolean三个内部运算转换的对照表。而当它们要转换对象范例前,会先用上面说的ToPrimitive先转换对象为原始数据范例,再举行转换到所要的范例值。

不论怎样,包装对象很少会被运用到,平常我们只会直接运用原始数据范例的值。而强迫转换函数由于也有替代的语法,它们会被用到的时机也不多。

实例

字符串 + 其他原始范例

字符串在加号运算有最高的优先运算,与字符串相加必定是字符串衔接运算(concatenation)。一切的其他原始数据范例转为字符串,能够参考ECMAScript规范中的ToString对照表,以下为一些简朴的例子:

> '1' + 123
"1123"

> '1' + false
"1false"

> '1' + null
"1null"

> '1' + undefined
"1undefined"

数字 + 其他的非字符串的原始数据范例

数字与其他范例作相加时,除了字符串会优先运用字符串衔接运算(concatenation)的,其他都要遵照数字为优先,所以除了字符串之外的其他原始数据范例,都要转换为数字来举行数学的相加运算。假如邃晓这项划定规矩,就会很轻易的得出加法运算的效果。

一切转为数字范例能够参考ECMAScript规范中的ToNumber对照表,以下为一些简朴的例子:

> 1 + true //true转为1, false转为0
2

> 1 + null //null转为0
1

> 1 + undefined //undefined转为NaN
NaN

数字/字符串之外的原始数据范例作加法运算

当数字与字符串之外的,其他原始数据范例直接运用加号运算时,就是转为数字再运算,这与字符串完整无关。

> true + true
2

> true + null
1

> undefined + null
NaN

空数组 + 空数组

> [] + []
""

两个数组相加,依旧根据valueOf -> toString的递次,但由于valueOf是数组自身,所以会以toString的返回值才是原始数据范例,也就是空字符串,所以这个运算相当于两个空字符串在相加,遵照加法运算划定规矩第2步骤,是字符串衔接运算(concatenation),两个空字符串衔接末了得出一个空字符串。

空对象 + 空对象

> {} + {}
"[object Object][object Object]"

两个空对象相加,依旧根据valueOf -> toString的递次,但由于valueOf是对象自身,所以会以toString的返回值才是原始数据范例,也就是”[object Object]”字符串,所以这个运算相当于两个”[object Object]”字符串在相加,遵照加法运算划定规矩第2步骤,是字符串衔接运算(concatenation),末了得出一个”object Object”字符串。

然则这个效果有非常,上面的效果只是在Chrome浏览器上的效果(v55),怎样说呢?

有些浏览器比方Firefox、Edge浏览器会把{} + {}直译为相当于+{}语句,由于它们会以为以花括号开首({)的,是一个区块语句的开首,而不是一个对象字面量,所以会以为略过第一个{},把全部语句以为是个+{}的语句,也就是相当于强迫求出数字值的Number({})函数挪用运算,相当于Number("[object Object]")运算,末了得出的是NaN

迥殊注重: {} + {}在差别的浏览器有差别效果

假如在第一个(前面)的空对象加上圆括号(()),如许JS就会以为前面是个对象,就能够得出一样的效果:

> ({}) + {}
"[object Object][object Object]"

或是分开来先声明对象的变量值,也能够得出一样的效果,像下面如许:

> let foo = {}, bar = {};
> foo + bar;

注: 上面说的行动这与加号运算的第一个(前面)的对象字面值是否是个空对象无关,就算是内里有值的对象字面,比方{a:1, b:2},也是一样的效果。

注: 上面说的Chrome浏览器是在v55版本中的主控台直接运转的效果。别的旧版本有能够并非此效果。

空对象 + 空数组

上面一样的把{}看成区块语句的状况又会发作,不过此次一切的浏览器都邑有一致效果,假如{}(空对象)在前面,而[](空数组)在背面时,前面(左侧)谁人运算元会被以为是区块语句而不是对象字面量。

所以{} + []相当于+[]语句,也就是相当于强迫求出数字值的Number([])运算,相当于Number("")运算,末了得出的是0数字。

> {} + []
0

> [] + {}
"[object Object]"

迥殊注重: 所以假如第一个(前面)是{}时,背面加上其他的像数组、数字或字符串,这时候加号运算会直接变成一元正号运算,也就是强迫转为数字的运算。这是个圈套要警惕。

Date对象

Date对象的valueOftoString的两个要领的返回值:

  • valueOf要领返回值: 给定的时候转为UNIX时候(自1 January 1970 00:00:00 UTC起算),然则以微秒盘算的数字值

  • toString要领返回值: 本地化的时候字符串

Date对象上面有说起是首选范例为”字符串”的一种非常的对象,这与其他的对象的行动差别(平常对象会先挪用valueOf再挪用toString),在举行加号运算常常,它会优先运用toString来举行转换,末了必定是字符串衔接运算(concatenation),比方以下的效果:

> 1 + (new Date())
> "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"

要得出Date对象中的valueOf返回值,须要运用一元加号(+),来强迫转换它为数字范例,比方以下的代码:

> +new Date()
1480180751492

Symbols范例

ES6中新到场的Symbols数据范例,它不算是平常的值也不是对象,它并没有内部自动转型的设想,所以完整不能直接用于加法运算,运用时会报错。

总结

{} + {}的效果是会因浏览器而有差别效果,Chrome(v55)中是[object Object][object Object]字符串衔接,但别的的浏览器则是以为相当于+{}运算,得出NaN数字范例。

{} + []的效果是相当于+[],效果是0数字范例。

参考文章

    原文作者:eyesofkids
    原文地址: https://segmentfault.com/a/1190000008038678
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞