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 ...
随机推荐
- ABP框架详解(六)Aspects
这种AOP式的设计非常类似于Asp.net MVC和WebApi中过滤器(Filter)机制,感觉没有太多可讲述的,只能谈谈设计思路. 框架中AspectAttribute特性用于设置到需要被拦截的T ...
- 为什么没调用 didRegisterForRemoteNotificationsWithDeviceToken 和 didFailToRegisterForRemoteNotificationsWithError
一步一步按照网上 push notification 教程走下来,发现didRegisterForRemoteNotificationsWithDeviceToken 和 didFailToRegis ...
- 【leetcode】Merge Two Sorted Lists
Merge two sorted linked lists and return it as a new list. The new list should be made by splicing t ...
- 用php创建mysql数据库
接触php就等于向后台更近了一步,之前一直在做前端,不过也在学php,但一直没敢写博客,现在终于有勇气迈向了这一步,还请各位博友多多担待. 服务器是后台开发的必备工具,但对于一般初学者来说是没有自己的 ...
- Http规范
1. 关于 HTTP Basic Authentication http://blog.itpub.net/23071790/viewspace-709367/ 通过以下代码,提示用户登录 Respo ...
- 【Bugly干货分享】一起用 HTML5 Canvas 做一个简单又骚气的粒子引擎
Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言 好吧,说是“粒子引擎”还是大言不 ...
- Android多线程分析之四:MessageQueue的实现
Android多线程分析之四:MessageQueue的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前面两篇文章<Androi ...
- SharePoint—用REST方式访问列表
REST的定义与作用 在SharePoint 2010中,基本上有如下几种数据访问方式: 服务器端对象模型 LINQ to SharePoint Web Service 客户端对象模型 ADO.NET ...
- 在js中对时间类型格式化字符串
Date.prototype.toString = function (format) { if (format == null) { format = "yyyy-MM-dd HH:mm: ...
- synchronized同步对象锁
package com.system.util; import com.common.Constants; import com.util.Cache; /** * 创建同步对象锁 * * @auth ...