JavaScript中{}+{}
在 JavaScript 中,加法的规则其实很简单,只有两种情况:
把数字和数字相加
把字符串和字符串相加
所有其他类型的值都会被自动转换成这两种类型的值。 为了能够弄明白这种隐式转换是如何进行的,我们首先需要搞懂一些基础知识。
注意:在下面的文章中提到某一章节的时候(比如§9.1),指的都是 ECMA-262 语言规范(ECMAScript 5.1)中的章节。
让我们快速的复习一下。 在 JavaScript 中,一共有两种类型的值:
原始值(primitives)
undefined
null
boolean
number
string
对象值(objects)。
除了原始值外,其他的所有值都是对象类型的值,包括数组(array)和函数(function)。
类型转换
加法运算符会触发三种类型转换:
转换为原始值
转换为数字
转换为字符串
通过 ToPrimitive() 将值转换为原始值
JavaScript 引擎内部的抽象操作 ToPrimitive() 有着这样的签名:
ToPrimitive(input,PreferredType?)
可选参数 PreferredType 可以是 Number 或者 String。 它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型(汗),但转换结果一定是一个原始值。 如果 PreferredType 被标志为 Number,则会进行下面的操作来转换 input (§9.1):
如果
input是个原始值,则直接返回它。否则,如果
input是一个对象。则调用obj.valueOf()方法。 如果返回值是一个原始值,则返回这个原始值。否则,调用
obj.toString()方法。 如果返回值是一个原始值,则返回这个原始值。否则,抛出
TypeError异常。
如果 PreferredType 被标志为 String,则转换操作的第二步和第三步的顺序会调换。 如果没有 PreferredType 这个参数,则 PreferredType 的值会按照这样的规则来自动设置:
Date类型的对象会被设置为String,其它类型的值会被设置为
Number。
通过 ToNumber() 将值转换为数字
下面的表格解释了 ToNumber() 是如何将原始值转换成数字的 (§9.3)
| 参数 | 结果 |
|---|---|
| undefined | NaN |
| null | +0 |
| boolean | true被转换为1,false转换为+0 |
| number | 无需转换 |
| string | 由字符串解析为数字。例如,"324"被转换为324 |
如果输入的值是一个对象,则会首先会调用 ToPrimitive(obj, Number) 将该对象转换为原始值, 然后在调用 ToNumber() 将这个原始值转换为数字。
通过ToString()将值转换为字符串
下面的表格解释了 ToString() 是如何将原始值转换成字符串的(§9.8)。
| 参数 | 结果 |
|---|---|
| undefined | "undefined" |
| null | "null" |
| boolean | "true" 或者 "false" |
| number | 数字作为字符串。比如,"1.765" |
| string | 无需转换 |
如果输入的值是一个对象,则会首先会调用 ToPrimitive(obj, String) 将该对象转换为原始值, 然后再调用 ToString() 将这个原始值转换为字符串。
实践一下
下面的对象可以让你看到引擎内部的转换过程。
var obj = {
valueOf: function () {
console.log("valueOf");
return {}; // not a primitive
},
toString: function () {
console.log("toString");
return {}; // not a primitive
}
}
Number 作为一个函数被调用(而不是作为构造函数调用)时,会在引擎内部调用 ToNumber() 操作:
> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
加法
有下面这样的一个加法操作。
value1 + value2
在计算这个表达式时,内部的操作步骤是这样的 (§11.6.1):
将两个操作数转换为原始值 (以下是数学表示法的伪代码,不是可以运行的 JavaScript 代码):
javascript prim1 := ToPrimitive(value1) prim2 := ToPrimitive(value2)PreferredType被省略,因此Date类型的值采用String,其他类型的值采用Number。如果 prim1 或者 prim2 中的任意一个为字符串,则将另外一个也转换成字符串,然后返回两个字符串连接操作后的结果。
否则,将 prim1 和 prim2 都转换为数字类型,返回他们的和。
预料到的结果
当你将两个数组相加时,结果正是我们期望的:
> [] + []
''
[] 被转换成一个原始值:首先尝试 valueOf() 方法,该方法返回数组本身(this):
> var arr = [];
> arr.valueOf() === arr
true
此时结果不是原始值,所以再调用 toString() 方法,返回一个空字符串(string 是原始值)。 因此,[] + [] 的结果实际上是两个空字符串的连接。
将一个数组和一个对象相加,结果依然符合我们的期望:
> [] + {}
'[object Object]'
解析:将空对象转换成字符串时,产生如下结果。
> String({})
'[object Object]'
所以最终的结果其实是把 "" 和 "[object Object]" 两个字符串连接起来。
更多的对象转换为原始值的例子:
> 5 + new Number(7)
12
> 6 + { valueOf: function () { return 2 } }
8
> "abc" + { toString: function () { return "def" } }
'abcdef'
意想不到的结果
如果 + 加法运算的第一个操作数是个空对象字面量,则会出现诡异的结果(Firefox console 中的运行结果):
> {} + {}
NaN
天哪!神马情况?(译注:这个问题的原因是,JavaScript 把第一个 {} 解释成了一个空的代码块(code block)并忽略了它。 NaN 其实是表达式 +{} 计算的结果 (+ 加号以及第二个 {})。 你在这里看到的 + 加号并不是二元运算符「加法」,而是一个一元运算符,作用是将它后面的操作数转换成数字,和 Number() 函数完全一样。例如:
> +"3.65"
3.65
以下的表达式是它的等价形式:
+{}
Number({})
Number({}.toString()) // {}.valueOf() isn’t primitive
Number("[object Object]")
NaN
为什么第一个 {} 会被解析成代码块(code block)呢? 因为整个输入被解析成了一个语句:如果左大括号出现在一条语句的开头,则这个左大括号会被解析成一个代码块的开始。 所以,你也可以通过强制把输入解析成一个表达式来修复这样的计算结果: (译注:我们期待它是个表达式,结果却被解析成了语句)
> ({} + {})
'[object Object][object Object]'
一个函数或方法的参数也会被解析成一个表达式:
> console.log({} + {})
[object Object][object Object]
经过前面的讲解,对于下面这样的计算结果,你也应该不会感到吃惊了:
> {} + []
0
在解释一次,上面的输入被解析成了一个代码块后跟一个表达式 +[]。 转换的步骤是这样的:
+[]
Number([])
Number([].toString()) // [].valueOf() isn’t primitive
Number("")
0
有趣的是,Node.js 的 REPL 在解析类似的输入时,与 Firefox 和 Chrome(和Node.js 一样使用 V8 引擎) 的解析结果不同。 下面的输入会被解析成一个表达式,结果更符合我们的预料:
> {} + {}
'[object Object][object Object]'
> {} + []
'[object Object]'
在大多数情况下,想要弄明白 JavaScript 中的 + 号是如何工作的并不难:你只能将数字和数字相加或者字符串和字符串相加。 对象值会被转换成原始值后再进行计算。如果将多个数组相加,可能会出现你意料之外的结果。
如果你想连接多个数组,需要使用数组的 concat 方法:
> [1, 2].concat([3, 4])
[1, 2, 3, 4]
JavaScript 中没有内置的方法来“连接” (合并)多个对象。 你可以使用一个 JavaScript 库,比如 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}
JavaScript中{}+{}的更多相关文章
- javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈
Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...
- javascript中的this与函数讲解
前言 javascript中没有块级作用域(es6以前),javascript中作用域分为函数作用域和全局作用域.并且,大家可以认为全局作用域其实就是Window函数的函数作用域,我们编写的js代码, ...
- JavaScript 中的数据类型
Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...
- javascript中的操作符详解1
好久没有写点什么了,根据博主的技术,仍然写一点javascript新手入门文章,接下来我们一起来探讨javascript的操作符. 一.前言 javascript中有许多操作符,但是许多初学者并不理解 ...
- 掌握javascript中的最基础数据结构-----数组
这是一篇<数据结构与算法javascript描述>的读书笔记.主要梳理了关于数组的知识.部分内容及源码来自原作. 书中第一章介绍了如何配置javascript运行环境:javascript ...
- javascript中变量提升的理解
网上找了两个经典的例子 var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); // 10 var ...
- 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型
前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- Javascript中的valueOf与toString
基本上,javascript中所有数据类型都拥有valueOf和toString这两个方法,null除外.它们俩解决javascript值运算与显示的问题,本文将详细介绍,有需要的朋友可以参考下. t ...
- 关于javascript中的this关键字
this是非常强大的一个关键字,但是如果你不了解它,可能很难正确的使用它. 下面我解释一下如果在事件处理中使用this. 首先我们讨论一下下面这个函数中的this关联到什么. function doS ...
随机推荐
- oracle常用语句总结
一.用户管理类 1.创建用户: Create user username Identified by password Default tablespace tablespacename Tempor ...
- Bullet物理引擎在OpenGL中的应用
Bullet物理引擎在OpenGL中的应用 在开发OpenGL的应用之时, 难免要遇到使用物理来模拟OpenGL中的场景内容. 由于OpenGL仅仅是一个关于图形的开发接口, 因此需要通过第三方库来实 ...
- [转载]并发编程之Operation Queue和GCD
并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...
- Ubuntu iptables配置
sudo su sudo apt-get install iptables-persistent modprobe ip_tables #启动iptable #删除原有iptables规则 ipt ...
- 【转】yahoo前端优化军规
雅虎给出了前端优化的34条法则(包括Yslow规则22条) 详细说明,下载转发 ponytail 的译文(来自帕兰映像). Minimize HTTP Requests 减少http请求 图片.css ...
- ListView设置headerview和footerview
[简介]headerview就是通常看到的那种listview手势下滑露出上面的部分,下拉到一定位置,松手会开始请求网络数据,然后刷新listview的列表.footerview一般就是listvie ...
- 冲刺阶段 day 14
项目进展 经过这几个星期的努力,我们已经完成了我们的软件工程项目,经过多次测试,项目已经可以准确无误地运行. 存在问题 测试期间,未发现问题. 心得体会 在这几个星期的努力下,我们终于完成了我们预期的 ...
- 基础调试命令 - u/ub/uf
在调试过程中难免会遇到需要反编译代码来分析逻辑的时候,在windbg中,需要反编译代码就要用到u/ub/uf这三个命令.本文这里分别介绍这三个命令各自的用途. 以下是一个quick sort的实例代码 ...
- Spring-Context之三:使用XML和Groovy DSL配置Bean
在第一讲中显示了如何使用注解配置bean,其实这是Spring3引进的特性,Spring2使用的是XML的方式来配置Bean,那时候漫天的XML文件使得Spring有着配置地狱的称号.Spring也一 ...
- mysql --prompt
mysql --prompt修改命令行链接mysql时的提示符,shell脚本示例如下 #!/bin/bash in crm) cmd='mysql -h192.168.1.2 -uroot -pro ...