在JS中的运算符合营的状况中,(+)标记是很罕见的一种,它有以下的运用状况:
数字的加法运算,二元运算
字符串的衔接运算,二元运算,最高优先
正号,一元运算,可延长为强迫转换其他范例的运算元为数字范例
固然,假如斟酌多个标记一同运用时,(+=)与(++)又是别的的用处。
另一个罕见的是花括号({}),它有两个用处也很罕见:
对象的字面笔墨定义
区块语句
所以,要能回复这个题目,要先搞清楚重点是什么?
第一个重点是:
加号(+)运算在JS中在运用上的划定是什么。
第二个重点则是:
对象在JS中是怎样转换为原始数据范例的值的。
加号运算符(+)
除了上面申明的罕见状况外,在规范中转换的划定规矩另有以下几个,要注重它的递次:
operand + operand = result
运用
ToPrimitive
运算转换左与右运算元为原始数据范例值(primitive)在第1步转换后,假如有运算元涌现原始数据范例是”字符串”范例值时,则另一运算元作强迫转换为字符串,然后作字符串的衔接运算(concatenation)
在其他状况时,一切运算元都邑转换为原始数据范例的”数字”范例值,然后作数学的相加运算(addition)
ToPrimitive内部运算
因而,加号运算符只能运用于原始数据范例,那末关于对象范例的值,要怎样转换为原始数据范例?下面申明是怎样转换为原始数据范例的。
在ECMAScript 6th Edition #7.1.1,有一个笼统的ToPrimitive
运算,它会用于对象转换为原始数据范例,这个运算不只会用在加号运算符,也会用在关联比较或值相称比较的运算中。下面有关于ToPrimitive
的申明语法:
ToPrimitive(input, PreferredType?)
input
代表代入的值,而PreferredType
可所以数字(Number)或字符串(String)个中一种,这会代表”优先的”、”首选的”的要举行转换到哪种原始范例,转换的步骤会依这里的值而有所差别。但假如没有供应这个值也就是预设状况,则会设置转换的hint
值为"default"
。这个首选的转换原始范例的指导(hint
值),是在作内部转换时由JS视状况自动加上的,平常状况就是预设值。
而在JS的Object
原型的设想中,都肯定会有两个valueOf
与toString
要领,所以这两个要领在一切对象内里都邑有,不过它们在转换有能够会交流被挪用的递次。
当PreferredType为数字(Number)时
当PreferredType
为数字(Number)时,input
为要被转换的值,以下是转换这个input
值的步骤:
假如
input
是原始数据范例,则直接返回input
。不然,假如
input
是个对象时,则挪用对象的valueOf()
要领,假如能获得原始数据范例的值,则返回这个值。不然,假如
input
是个对象时,挪用对象的toString()
要领,假如能获得原始数据范例的值,则返回这个值。不然,抛出TypeError毛病。
当PreferredType为字符串(String)时
上面的步骤2与3对换,犹以下面所说:
假如
input
是原始数据范例,则直接返回input
。不然,假如
input
是个对象时,挪用对象的toString()
要领,假如能获得原始数据范例的值,则返回这个值。不然,假如
input
是个对象时,则挪用对象的valueOf()
要领,假如能获得原始数据范例的值,则返回这个值。不然,抛出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
,平常状况数字范例是首选范例。
上面说的ToString
与ToNumber
这两个也是JS内部的笼统运算。
valueOf与toString要领
valueOf
与ToString
是在Object中的两个必有的要领,位于Object.prototype上,它是对象要转为原始数据范例的两个姐妹要领。从上面的内容已能够看到,ToPrimitive
这个笼统的内部运算,会遵照设置的首选的范例,决议要前后挪用valueOf
与toString
要领的递次,当数字为首选范例时,优先运用valueOf
,然后再挪用toString
。当字符串为首选范例时,则是相反的递次。预设挪用体式格局则是如数字首选范例一样,是先挪用valueOf
再挪用toString
。
JS关于Object与Array的设想
在JS中所设想的Object
纯对象范例的valueOf
与toString
要领,它们的返回以下:
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
有掩盖,申明一下数组的valueOf
与toString
的两个要领的返回值:
valueOf
要领返回值: 对象自身。(与Object一样)toString
要领返回值: 相当于用数组值挪用join(',')
所返回的字符串。也就是[1,2,3].toString()
会是"1,2,3"
,这点要迥殊注重。
Function对象很少会用到,它的toString
也有被掩盖,所以并非Object中的谁人toString
,Function对象的valueOf
与toString
的两个要领的返回值:
valueOf
要领返回值: 对象自身。(与Object一样)toString
要领返回值: 函数中包括的代码转为字符串值
Number、String、Boolean三个包装对象
包装对象是JS为原始数据范例数字、字符串、布尔特地设想的对象,一切的这三种原始数据范例所运用到的属性与要领,都是在这上面所供应。
包装对象的valueOf
与toString
的两个要领在原型上有经由掩盖,所以它们的返回值与平常的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规范中的ToNumber
、ToString
、ToBoolean
三个内部运算转换的对照表。而当它们要转换对象范例前,会先用上面说的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对象的valueOf
与toString
的两个要领的返回值:
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
数字范例。