【译】在JavaScript中{}+{}的结果是什么?
原文链接:What is {} + {} in JavaScript?
最近,Gary Bernhardt在一个名为'Wat'的闪电演讲中提到了一些有趣的JavaScript技巧。当你把一个object和一个object或者和一个数组相加时,会获得意想不到的结果。这篇文章将会对这些结果做解释。
在JavaScript中,对于+的普通规则很简单:你可以把数字和字符串相加,结果将会被转换成他们中的一种类型。为了理解这种类型转换时如何工作的,我首先需要了解一些其他东西。无论何时引用一个段落,它都引自ECMA-262语言标准。
让我们一起回顾下。在JavaScript中有两种变量:原始类型和引用类型。原始类型包括:null, undefined, booleans, numbers和字符串。 其他的变量都是引用类型,包括字符串和函数。
1.转换值
加号运算符表现为3种类型的转换:分别是转换为原始值,数字和字符串。
1.1. 通过ToPrimitive()将变量转换成原始值
内部方法ToPrimitive()如下:
ToPrimitive(input , PreferredType?)
可选参数PreferredType是字符串或者数字。 它只是表达一种倾向,结果可以是任何一个原始值。如果PreferredType是Number类型,它将会按照以下步骤转换:
1. 如果输入值是一个原始值,则返回它。
2. 否则, 输入值是一个对象, 调用obj.valueof()。如果结果是原始值,返回。
3.否则,调用obj.toString().如果结果是原始值,返回它。
4. 否则,抛出一个类型错误。
如果PreferredType 是一个字符串,步骤2和步骤3交换。如果PreferredType没有指明,对于日期的类型,将会被设置为String,对于其他类型的值将会被设置为Number。
1.2 通过ToNumber()将变量转换为数字
下面的表格解释了ToNumber是如何把原始值转换为number的

1.3 通过ToString()将变量转换为字符串
一个对象通过调用ToPrimitive(obj, Number)转换为数字,然后对得到的primitive结果调用ToNumber。
下面的表格解释了ToString()是如何把原始值转换为字符串的。

一个对象通过调用ToPrimitive(obj, Number)转换为数字,然后对得到的primitive结果调用ToString。
1.4 Trying it out
下面的对象允许你观察转换的过程。
var obj = {
valueOf: function () {
console.log(‘valueOf’);
return {};
},
toString: function() {
console.log(‘toString’);
return {};
}
}
Number 作为一个函数调用(而不是作为一个构造函数)内部会调用ToNumber():
>Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
2. 相加
给出下面的一个加法.
value1 + value2
为了计算这个表达式, 会按照以下步骤执行:
1. 转换两个操作数为原始值(数学符号,而不是JavaScript运算符)
prim1 := ToPrimitive(value1)
prim2 := ToPrimitive(value2)
PreferredType参数被省略了,因此对于日期类型被设置为String,对于非日期类型设置为Number。
2. 如果prim1或者prim2是字符串, 那么将它们都转换为字符串,然后返回字符串连接的结果。
3. 否则, 将prim1和prim2转换为数字并返回结果的相加
2.1 意料内的结果
当你将两个数组相加, 所有的事情都会像预期一样:
[] + [] ""
将[]转换为原始值,首先会尝试valueOf,她返回数组本身:
var arr = []; arr.valueOf() === arr true
结果不是原始值, 接下来调用toString并且返回空字符串(这个是原始值)。因此,[] + []的结果就是两个空字符串相加。
将一个数组和一个对象相加同样也符合我们的预期:
[] + {}
"[object object]"
解释:将空动向转换为字符串得到如下结果。
String({})
"[object Object]"
因此前面的结果就是通过连接 "" 和 "[object Object]"。
看下更多的例子, 对象被转换为原始值:
5 + new Number(7)
12 6 + {valueOf: function () {return 2}}
8 "abc" + {toString: function () {return "def"} }
"abcdef"
2.2 意向不到的结果
如果+的第一个操作数是一个空对象,事情就变得奇怪了(结果正如你在Firefox的console中所见)
{} + {}
NaN
在这为什么会这样?问题就是JavaScript把第一个{}解释成了一个空的代码块,并且忽略它。 因此NaN结果就是通过计算+{}得到的(加上第二个{})。你在这看的加不是一个二进制相加运算符,而是一个一元运算符,她将操作数转换为数字,和Number()是一样的。例如:
+ “3.65”
3.65
下面的表达式是等价的:
+ {}
Number({})
Number({}.toString()) // {}.valueOf() 不是一个原始值
Number(“[object Object]”)
NaN
为什么第一个{}会被解释成空代码块?因为完整的输入被解析为一个语句并且大括号在欲绝的开始被解释为一个代码块的开始。因此,你可以通过强制的将它变为一个表达式来解决。
({} + {})
"[object Object][object Object]"
函数或者方法的参数同样被解释为表达式:
console.log({} + {})
"[object Object][object Object]"
有了前面的解释, 你应该不会再对下面这个结果感到惊讶了:
{} + []
0
再来一次, 这个被解释为+ []. 下面的表达式是等价的:
+ [] Number([]) Number( [].toString()) // [].valueOf() 不是一个原始值 Number(“ “) 0
有趣的是, 在Node.js的REPL中, 会和FireFox或者Chrome(它甚至和Node.js同样用v8引擎)有不同的解释. 下面的输入被解析为表达式,结果令人有点小吃惊
{} + {}
"[object Object][object Object]"
{} + []
"[object Object]"
它有一个优势就是结果更像是把表达式作为console.log的参数。但是它却不太像在程序中使用这个输入作为语句。
3. What does it all mean?
在大多数情况中, 在JavaScript中理解+是如何工作的并不困难: 你可以仅仅是把数字或者字符串相加。对象被转换为字符串(如果有一个操作数是字符串)或者数字。如果你想连接两个数组, 你需要使用方法:
[1, 2].concat([3, 4]) [1, 2, 3, 4]
在JavaScript中没有一个内部方法来连接(merge)对象。你需要使用一个库,例如Underscore:
var o1 = {eeny:1, meeny:2};
var o2 = {miny:3, moe: 4};
_.extend(o1, o2)
{ eeny: 1,
meeny: 2,
miny: 3,
moe: 4 }
注释: 和Array.prototype.concat()相比,extend()改变了第一个参数:
o1
{ eeny: 1,
meeny: 2,
miny: 3,
moe: 4 }
o2
{ miny: 3, moe: 4 }
如果你对操作符有更大的兴趣, 你可以阅读这篇文章"Fake operator overloading in JavaScript"
(PS. 翻译可能会有个别错误或者不恰当, 还望指出,谢谢)
【译】在JavaScript中{}+{}的结果是什么?的更多相关文章
- 【译】Javascript中的数据类型
这篇文章通过四种方式获取Javascript中的数据类型:通过隐藏的内置[[Class]]属性:通过typeof运算符:通过instanceof运算符:通过函数Array.isArray().我们也会 ...
- (译)JavaScript 中的正则表达式(RegEx)实操——快速掌握正则表达式,伴有随手可练的例子————(翻译未完待续)
(原文:https://blog.bitsrc.io/a-beginners-guide-to-regular-expressions-regex-in-javascript-9c58feb27eb4 ...
- [译]在Javascript中进行日期相关的操作
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- [译]在Javascript中制造二维数列
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- javaScript中的严格模式 (译)
“use strict”状态指示浏览器使用严格模式,是javaScript中一个相对少且安全的特征集. 特征列表(非完全列举) 不允许定义全局变量.(捕获没有用var声明的变量和变量名的拼写错误) 在 ...
- javaScript中的闭包原理 (译)
这篇文章通过javaScript代码解释了闭包的原理,来让编程人员理解闭包.它不是写给大牛或使用功能性语言进行编程的程序员的.一旦意会了其核心概念,闭包理解起来并不难.然而,你不可能通过阅读任何有关闭 ...
- [译]JavaScript中,{}+{}等于多少?
最近,Gary Bernhardt在一个简短的演讲视频“Wat”中指出了一个有趣的JavaScript怪癖:在把对象和数组混合相加时,会得到一些你意想不到的结果.本篇文章会依次讲解这些计算结果是如何得 ...
- [译]Javascript中的错误信息处理(Error handling)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- [译]Javascript中的闭包(closures)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
随机推荐
- Js Pattern - Self Define Function
This pattern is useful when your function has some initial preparatory work to do andit needs to do ...
- C#类索引器的使用
索引器提供了一种可以让类被当作数组进行访问的方式.在C#中,类索引器是通过this的属性实现的.index.ToString("D2")将index转换成一个具有两个字符宽度的字符 ...
- POJ 2406 Power Strings KMP运用题解
本题是计算一个字符串能完整分成多少一模一样的子字符串. 原来是使用KMP的next数组计算出来的,一直都认为是能够利用next数组的.可是自己想了非常久没能这么简洁地总结出来,也仅仅能查查他人代码才恍 ...
- MKMapView的内存释放问题
MKMapView的内存释放问题 by 伍雪颖 - (void)dealloc { self.mapView.showsUserLocation = NO; self.mapView.userTrac ...
- 【Bootstrap3.0建站笔记三】AspNetPager分页,每一列都可排序
1.AspNetPager分页,实现每一列都可排序: (1).须要将默认排序字段放在HTML页面中. (2).排序字段放置为td节点的属性. 如图: 实现的效果 ...
- Tomcat7集群扩展session集中管理,tomcat-redis-session-manager使用
请参考官方文档 下载所需的包了: tomcat-redis-session-manager-1.1.jar jedis-2.1.0.jar commons-pool-1.6.jar 将这些jar包都丢 ...
- [ES6] 18. Map
ES6 provides Map, it is a set of k-v pair. Key can be number, string, object, function and even unde ...
- iOS开发——实用篇Swift篇&状态栏操作
状态栏操作 在Swift开发过程中,针对状态栏操作的过程有很多. 1.在ViewController中操作当前ViewController的状态栏 /** 隐藏状态栏 */ override func ...
- linux后端运行
程序命令 & :将命令放入后台运行. Ctrl + z : 把一个正在运行的前端命令转移到后台运行,它等效于:程序命令 & :这样虽然把程序放在了后端运行,但是此时程序状态为暂停状态, ...
- mysql中使用count()统计的特殊之处
如果你的需要是统计总行数时,为什么要使用count(*),而避免使用指定具体的列名? count()函数里面的参数是列名的的时候,那么会计算有值项的次数.也就是,该列没有值的项并不会进入计算范围.这样 ...