JS中的加号+运算符详解

加号+运算符

在 JavaScript 中,加法的规则其实很简单,只有两种情况:

  • 把数字和数字相加
  • 把字符串和字符串相加

所有其他类型的值都会被自动转换成这两种类型的值。 为了能够弄明白这种隐式转换是如何进行的,我们首先需要搞懂一些基础知识。

让我们快速的复习一下。 在 JavaScript 中,一共有两种类型的值:

  • 原始值(primitives)

    1. undefined
    2. null
    3. boolean
    4. number
    5. string
  • 对象值(objects):除了原始值外,其他的所有值都是对象类型的值,包括数组(array)和函数(function)。

类型转换

加法运算符会触发三种类型转换:

  1. 转换为原始值
  2. 转换为数字
  3. 转换为字符串

通过 ToPrimitive() 将值转换为原始值

JavaScript 引擎内部的抽象操作 ToPrimitive() 有着这样的签名:

ToPrimitive(input,PreferredType?)

可选参数 PreferredType 可以是 Number 或者 String。 它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型(汗),但转换结果一定是一个原始值。
如果 PreferredType 被标志为 Number,则会进行下面的操作来转换input

  1. 如果 input 是个原始值,则直接返回它。
  2. 否则,如果 input 是一个对象。则调用 obj.valueOf() 方法。 如果返回值是一个原始值,则返回这个原始值。
  3. 否则,调用 obj.toString() 方法。 如果返回值是一个原始值,则返回这个原始值。
  4. 否则,抛出 TypeError 异常。

如果 PreferredType 被标志为 String,则转换操作的第二步和第三步的顺序会调换。 如果没有 PreferredType 这个参数,则 PreferredType
的值会按照这样的规则来自动设置:

  • Date 类型的对象会被设置为 String
  • 其它类型的值会被设置为 Number

通过 ToNumber() 将值转换为数字

下面的表格解释了 ToNumber() 是如何将原始值转换成数字的

参数 结果
undefined NaN
null +0
boolean true被转换为1,false转换为+0
number 无需转换
string 由字符串解析为数字。例如,"324"被转换为324

如果输入的值是一个对象,则会首先会调用 ToPrimitive(obj, Number) 将该对象转换为原始值, 然后在调用 ToNumber() 将这个原始值转换为数字。

通过ToString()将值转换为字符串

下面的表格解释了 ToString() 是如何将原始值转换成字符串的

参数 结果
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

在计算这个表达式时,内部的操作步骤是这样的

  1. 将两个操作数转换为原始值 (以下是数学表示法的伪代码,不是可以运行的 JavaScript 代码):

     prim1 := ToPrimitive(value1)
     prim2 := ToPrimitive(value2)

    PreferredType 被省略,因此 Date 类型的值采用 String,其他类型的值采用 Number。

  2. 如果 prim1 或者 prim2 中的任意一个为字符串,则将另外一个也转换成字符串,然后返回两个字符串连接操作后的结果。
  3. 否则,将 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]‘

时间: 2024-11-10 12:05:01

JS中的加号+运算符详解的相关文章

js 中中括号,大括号使用详解

一.{ } 大括号,表示定义一个对象,大部分情况下要有成对的属性和值,或是函数.如:var LangShen = {"Name":"Langshen","AGE":"28"};上面声明了一个名为"LangShen"的对象,多个属性或函数用,(逗号)隔开,因为是对象的属性,所以访问时,应该用.(点)来层层访问:LangShen.Name.LangShen.AGE,当然我们也可以用数组的方式来访问,如:Lang

JS中的event 对象详解

JS中的event 对象详解 JS的event对象 Event属性和方法: 1. type:事件的类型,如onlick中的click: 2. srcElement/target:事件源,就是发生事件的元素: 3. button:声明被按下的鼠标键,整数,1代表左键,2代表右键,4代表中键,如果按下多个键,酒把这些值加起来,所以3就代表左右键同时按下:(firefox中 0代表左键,1代表中间键,2代表右键) 4. clientX/clientY:事件发生的时候,鼠标相对于浏览器窗口可视文档区域的

JS中的this用法详解

随着对js的深入学习和使用,你会发现它里面包含了很多令人困惑的机制,比如对象.闭包.原型链继承等等,而这其中肯定包含令你现在或者曾经费解的this,如果你不把心一横,花点时间还真不明白这个this的用法,下面这篇文章来详细探讨下: 1.this是啥? 简言之,this是JavaScript语言中定义的众多关键字之一,它的特殊在于它自动定义于每一个函数域内,但是this到底指啥东西却让很多人张二摸不着头脑.这里我们留个小悬念,希望看完这篇文章了你能回答出来this到底指什么. 2.this有啥用?

js中时间new Date()详解以及实例

介绍 var myDate = new Date(); myDate.getYear(); //获取当前年份(2位) myDate.getFullYear(); //获取完整的年份(4位,1970-????) myDate.getMonth(); //获取当前月份(0-11,0代表1月) // 所以获取当前月份是myDate.getMonth()+1; myDate.getDate(); //获取当前日(1-31) myDate.getDay(); //获取当前星期X(0-6,0代表星期天) m

JS中的this对象详解

    JS中this关键字很常见,但是它似乎变幻莫测,让人抓狂.这篇文章就来揭示其中的奥秘.      借助阮一峰老师的话:它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.这句话看似平常,可是要非常注意三个字:"运行时",这说明this关键字只与函数的执行环境有关,而与声明环境没有关系.也就是这个this到底代表的是什么对象要等到函数运行时才知道,有点类似函数定义时的参数列表只在函数调用时才传入真正的对象.理解了这一点对后面this关键字规律的掌握有很大帮助.     

js中三种作用域详解(全局,函数,块级)

1.全局变量:声明在函数外部的变量(所有没有var直接赋值的变量都属于全局变量) 2.局部变量:声明在函数内部的变量(所有没有var直接赋值的变量都属于全局变量) JS中变量申明分显式申明和隐式申明. vari=100;//显式申明 i=100;//隐式申明 在函数中使用var关键字进行显式申明的变量是做为局部变量,而没有用var关键字,使用直接赋值方式声明的是全局变量. 当我们使用访问一个没有声明的变量时,JS会报错.而当我们给一个没有声明的变量赋值时,JS不会报错,相反它会认为我们是要隐式申

Vue.js中学习使用Vuex详解

在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适:简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取.进行修改,并且你的修改可以得到全局的响应变更.下面咱们一步一步地剖析下vuex的使用:首先要安装.使用 vuex首先在 vue 2.0+ 你的vue-cli项目中安装 vuex : npm install vuex --save 然后 在src文件目录下新建一个名为store的文件

java中的三元运算符详解

最近在带领实习生中遇到很多新手问与三元运算符有关的java题目,多数为代码结果题,少数为应用题.鉴于很多资料上对于java三元运算的讲解过于简单,网上的资料与题目也不是很完善,对于结果答案分析不一,故在此总结,当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 什么是java三元运算符呢?无疑其操作元有三个,第一个是条件表达式,剩余两个为值,条件表达式为真时运算取第一个值,为假时取第二个值. 其示例代码如下:boolean a = 20 < 45 ? true : false

JS中的window.setTimeout()详解

相关用法: setTimeout (表达式,延时时间)setInterval (表达式,交互时间)其中延时时间/交互时间是以豪秒为单位的(1000ms=1s) setTimeout 在执行时,是在载入后延迟指定时间后,去执行一次表达式,仅执行一次setInterval 在执行时,它从载入后,每隔指定的时间就执行一次表达式 1,基本用法: var i=0;   setTimeout("i+=1;alert(i)",1000);   执行一个函数:   var i=0;   setTime