为什么JavaScript处理字符串和数字之间的加减运算符不同?

87

我不明白为什么JavaScript会以这种方式工作。

console.log("1" + 1);
console.log("1" - 1);

第一行打印11,第二行打印0。为什么JavaScript将第一行作为字符串处理,而将第二行作为数字处理?


12
尽管对于熟悉JS的人来说,答案“为什么”很明显,但“为什么显而易见的答案是正确的”这个原因仍然超出了我的理解 - 我想我不是唯一一个... JS在许多方面都违反了POLA 叹气。https://dev59.com/amox5IYBdhLWcg3wsGWC?rq=1 - user719662
5
我认为这个链接应该在任何涉及 JavaScript 奇怪行为的场合发布:https://www.destroyallsoftware.com/talks/wat - DLeh
@DLeh:我正要发布那个视频的链接:D - gen_Eric
2
还有相关内容:你的语言很烂 - Simon Forsberg
7个回答

121

字符串连接使用+操作符,因此Javascript将把第一个数字1转换为一个字符串并将"1"和"1"连接起来得到"11"。

不能对字符串执行减法运算,所以Javascript会将第二个"1"转换为一个数字,并从1中减去1,结果为0。


8
@YuryTarabanko 好的。连接(而不是加法)始终将2个字符串组合在一起。因此,如果您尝试执行[] + {},则基本上执行[].toString() + ({}).toString()(因为JavaScript在连接它们之前将涉及的数组和对象转换为字符串)。并且,因为[].toString === ''({}).toString() === '[object Object]',所以最终结果为[] + {} === '[object Object]'。这是完全合理的。 - Joeytje50
4
没问题。那么{} + []呢? :) 继续运用同样的逻辑吧 :) - Yury Tarabanko
3
由于对象和数组不能连接或相加,因此按照这个顺序将它们放在一起会导致数组被转换为数字而不是字符串,因为加号 + 在它前面(就像 +new Date 返回 Date 对象的数值值(UNIX 时间戳),或者 +true 返回 true 的数值值,即 1)。由于这个原因,加法变成了 {} + 0。因为对象没有数值,所以这变成了 +0,JavaScript 将其输出为 0 - Joeytje50
4
哈哈,那并不是*相同的逻辑。如果“对象和数组既不能连接也不能相加”,那么为什么[] + {}会执行连接,而{} + []却不会?此外,你所说的“对象没有数值”是错误的:+{}返回NaN。而NaN + 0NaN,而不是0。不过像@Yury所说的,从实际情况或常识的角度来讨论JavaScript类型强制转换是毫无意义的。 - Ajedi32
11
只是为了记录,JavaScript中行首的大括号表示代码块而不是对象字面量;因此[] + {}{} + []实际上是两个完全不同的语句。 - James Long
显示剩余6条评论

34

+是个有歧义的符号。它可以表示“连接”或“相加”。由于一侧是字符串,因此它被认为是“连接”,因此结果是11(顺便说一下,这是我小时候最喜欢的笑话之一。“1+1=窗户”,如图所示:│┼│ ニ ⊞

-只有一个意思:减去。所以它执行减法操作。

这种问题在其他语言中如PHP中不存在,其中“连接”用的是.而不是+,因此没有歧义。而其他语言比如MySQL甚至没有连接运算符,而是使用CONCAT(a,b,c...)代替。


9
避免这个问题(以及JavaScript中出现的许多其他问题)的另一种解决方案是不允许隐式转换。例如,Python在您尝试类似于上述操作时会直接抛出错误,从而避免了所有这些不直观的问题。在动态类型语言中进行隐式转换是一个可怕的想法。 - Voo

26
因为规范明确要求如此。第75页。请注意11.6.1步骤5-8和11.6.2步骤5-7之间的差异。
11.6.1 - 描述加法运算符的工作原理
1-4. ...
5. 让lprim成为ToPrimitive(lval)。
6. 让rprim成为ToPrimitive(rval)。
7. 如果Type(lprim)是String或Type(rprim)是String,则
7a. 返回将ToString(lprim)与ToString(rprim)连接起来的字符串。
8. 返回将ToNumber(lprim)和ToNumber(rprim)应用于加法运算的结果。
11.6.2 - 描述减法运算符的工作原理
1-4. ...

5. 让lnum成为lval的数字形式。

6. 让rnum成为rval的数字形式。

7. 返回将lnum和rnum进行减法操作后得到的结果。

概述 在加法运算中,如果任何一个操作数在没有任何提示的情况下被转换为原始值时突然变成字符串,则第二个操作数也会被转换为字符串。在减法运算中,两个操作数都被转换为数字。


1
例如,试着想象一下为什么 [] + [] === "" :) 是由于连接和加法的歧义吗?LOL - Yury Tarabanko
4
+1 是因为这是唯一权威的答案。其他所有内容可能是有用的记忆技巧,但最终答案是“因为规范是这样说的”,而它之所以这样说,是因为 Brendan Eich 在那些臭名昭著的 10 天里认为这是个好主意。 - Matteo Italia

12
在JavaScript中没有专门的字符串连接运算符。加法运算符+根据操作数的类型,执行字符串连接或加法操作:
"1" +  1  // "11"
 1  + "1" // "11"
 1  +  1  // 2

我认为没有连接操作的相反操作,减法运算符-不考虑操作数的类型只执行减法。

"1" -  1  // 0
 1  - "1" // 0
 1  -  1  // 0
"a" -  1  // NaN

PHP中的.操作符和VB中的&操作符均用于字符串连接。


8

+是数字变量的加法运算符,也是字符串的连接运算符

每当+后面跟着一个字符串时,Javascript会选择将+作为连接运算符,并转换尽可能多的与该字符串相关的项,以便将它们连接起来。这就是Javascript的行为方式。(如果你尝试console.log(23 + 2 + "." + 1 + 5 + "02" + 02);,你将得到结果25.15022。数字02在被连接之前被转换成了字符串2

-只能是减法运算符,所以当给定字符串时,它会隐式地将字符串"1"类型转换成数字1;如果不这样做,就没有办法使"1" - 1有意义。如果你尝试console.log(23 + 2 + 1 + 5 - "02" + 03);,你会得到32 - 字符串02被转换为数字2。减号后的项必须能够转换为数字;如果你尝试console.log(23 - 2 - "." - 1 - 5 - 02 - "02");,你将得到返回NaN

更重要的是,如果你尝试console.log(23 + 2 + "." + 1 + 5 - "02" + 03);,它将输出26.15,其中-之前的所有内容都被视为字符串(因为它包含一个字符串"."),然后-后的项被视为数字。


在你的最后一个例子中,似乎JS是从左到右考虑元素的:它先将数字23 + 2 = 25相加,然后找到字符串“.”并将类型数字25转换为类型字符串“25”,并将1和5连接起来,以获得类型字符串“25.15”。此时,它找到了“-”,因此将前面和后面的字符串强制转换为数字,并且由于两者都可以转换为数字,所以执行减法运算以获得数字23.15。最后,它将3添加到最终结果中,类型为数字26.15 - Ferie

5
根据EcmaScript 262标准,当涉及字符串时,+-运算符的行为不同。第一个将每个值转换为字符串。第二个将每个值转换为数字。
从标准中可以看出:如果lprim的类型是String或rprim的类型是String,则返回连接ToString(lprim)后跟ToString(rprim)的结果字符串。
这条规则意味着,如果表达式中有一个字符串值,则与+操作涉及的所有值都将转换为字符串。在JavaScript中,当与字符串一起使用+运算符时,它们会连接在一起。这就是为什么console.log(“5”+1)返回“51”的原因。 1被转换为字符串,然后“5”和“1”被连接在一起。
尽管如此,上述规则不适用于-运算符。当使用-时,根据标准,所有值都将转换为数字(见下文)。因此,在这种情况下,“5”会转换为5,然后减去1

从标准:

5 让lnum成为lval的数字形式。

6 让rnum成为rval的数字形式。


从标准EcmaScript 262中定义的运算符。

运算符 +:http://www.ecma-international.org/ecma-262/5.1/#sec-11.6.1 运算符+定义

运算符 -:http://www.ecma-international.org/ecma-262/5.1/#sec-11.6.2 运算符-定义


喜欢看到人们阅读和引用规格说明书和手册。谢谢。 - user900360

0
使用加号和字符串"",你基本上返回一个字符串,因为你正在执行连接操作:
typeof ("" + 1 + 0)  // string
typeof (1 + 0)  // number

当使用-时,你可以将其转换为数字,因为字符串连接是可行的:

typeof ("" - 1 + 0) // number

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接