为什么没有逻辑异或运算符?

287
为什么JavaScript中没有逻辑的XOR运算符?
21个回答

445

JavaScript的起源可以追溯到C语言,而C语言并没有逻辑XOR运算符。主要是因为它没有什么用处。按位XOR非常有用,但在我多年的编程生涯中,我从未需要过逻辑XOR。

如果你有两个布尔变量,你可以用以下方式模拟XOR:

if (a != b)

有两个任意变量,你可以使用!将它们强制转换为布尔值,然后使用相同的技巧:

if (!a != !b)

这虽然相当晦涩,但肯定值得注释。实际上,在这一点上,甚至可以使用按位异或运算符,但这对我来说过于聪明了:

if (!a ^ !b)

!= 的唯一问题是你不能像 a ^= b 那样做,因为 a !== b 只是 严格不等 运算符。 - mcpiroman
3
只要你已经有了两个布尔变量ab,异或运算确实是多余的。但通常情况下,人们在拥有布尔变量时并不需要使用布尔运算。你永远不会这样做:const a = foo == bar; if (a == true) { console.log("foo=bar"); }布尔运算的关键点在于允许进行简单的内联测试,由编译器进行优化,无需定义额外的变量。 - Bogdan Stăncescu

89

Javascript有一个按位异或运算符:^

var nb = 5^9 // = 12

你可以将布尔值用于这个操作,它会返回0或1(你可以将其转换回布尔值,例如result = !!(op1 ^ op2))。但正如John所说,这等同于result = (op1 != op2),更加清晰易懂。


77
你可以将它用作逻辑异或运算。 true^true 等于 0,而 false^true 则等于 1。 - Pik'
17
@Pikrass,你可以将它用作布尔值的逻辑运算符,但不能用于其他类型。 ||&& 可以用作非布尔类型的逻辑运算符(例如 5 || 7 返回一个truthy值,"bob" && null 返回一个falsey值),但 ^ 不行。例如,5 ^ 7 等于2,这是一个truthy值。 - Mark Amery
13
很遗憾,(true ^ false) !== true,这让在需要实际布尔值的库中使用变得很麻烦。 - Izkata
4
@Pikrass,你不应该将它用作布尔逻辑运算符,因为其实现取决于操作系统。我曾经使用过一种a ^= true的方法来切换布尔值,但在某些机器上(如手机)会失败。 - Masadow
2
@cronvel 当然,有时候你会尝试缩短超长的路径,比如 myObj.collection[index].someImportantFlag = !myObj.collection[index].someImportantFlag,所以使用 ^= true 更方便。但我再也不会被诱惑了 :) - Masadow
显示剩余5条评论

37

两个布尔值的XOR操作仅仅是判断它们是否不同,因此:

Boolean(a) !== Boolean(b)

2
我不得不使用这个解决方案,因为当你试图在TypeScript中使用^操作符与布尔值一起使用时会报错。 - Nathan Arthur

33
在Javascript中,没有真正的逻辑布尔运算符(虽然!接近)。逻辑运算符只会将truefalse作为操作数,并仅返回truefalse
在Javascript中,&&||接受各种操作数并返回各种有趣的结果(无论您输入什么)。
逻辑运算符应始终考虑两个操作数的值。但在Javascript中,&&||采用一种懒惰方法并且在某些情况下不评估第二个操作数,从而忽略了其副作用。这种行为无法使用逻辑异或来重新创建。
因此,a() && b()将评估a(),如果它为假,则返回结果。否则,它将评估b()并返回结果。因此,如果两个结果都是真值,则返回结果为真值,否则为假值。
相似地,a() || b()将评估a(),如果它为真,则返回结果。否则,它将评估b()并返回结果。因此,如果两个结果都是假值,则返回结果为假值,否则为真值。
因此,一般思路是首先评估左操作数,只有在必要时才评估右操作数。最后的值是结果。该结果可以是任何东西。对象、数字、字符串...任何东西!
这使得编写如下代码成为可能:
image = image || new Image(); // default to a new Image
或者
src = image && image.src; // only read out src if we have an image

但是这个结果的真值也可以用来决定一个“真正”的逻辑运算符是否会返回true或false。

这使得像下面这样的写法成为可能:

if (typeof image.hasAttribute === 'function' && image.hasAttribute('src')) {
或者
if (image.hasAttribute('alt') || image.hasAttribute('title')) {

然而,“逻辑”异或运算符(^^)始终必须评估两个操作数。这使它与其他“逻辑”运算符不同,后者仅在必要时评估第二个操作数。我认为这就是为什么 JavaScript 中没有“逻辑”异或的原因,以避免混淆。


那么如果两个操作数都为假值会发生什么?两个都可以返回。但只能返回一个。哪个?第一个吗?还是第二个?我的直觉告诉我返回第一个,但通常“逻辑”运算符从左到右进行求值并返回最后一个求值的值。或者可能返回包含两个值的数组?

如果一个操作数为真值,另一个操作数为假值,则异或应该返回真值。或者可能返回包含真值的数组,以使其与前面的情况兼容?

最后,如果两个操作数都为真值,您应该期望得到某些假值。但是没有假值结果。因此该操作不应返回任何内容。所以也许是 undefined 或..一个空数组?但是空数组仍然是真值。

采用数组方法,您将得到像 if ((a ^^ b).length !== 1) { 这样的条件,非常令人困惑。


在任何语言中,XOR/^^ 操作符都必须评估两个操作数,因为它始终依赖于两者。对于AND/&& 操作符也是如此,因为所有操作数都必须为真(在 JS 中为 truthy),才会返回 pass。唯一的例外是 OR/|| 操作符,因为它只需要评估操作数,直到找到一个 truthy 值为止。如果 OR 列表中的第一个操作数为 truthy,则不会评估其他操作数。 - Percy
9
如果第一个操作数为假,@Percy AND/&& 不会评估第二个操作数。它只会评估操作数,直到找到一个错误值。 - Robert
我的编辑被拒绝了,之后@matts按照我修正的方式重新编辑了它,所以我错过了我微不足道的2个点。3个人拒绝了它,我不明白他们使用了什么标准。谢谢matts。 - DDS
@DDS 哎呀,我不是故意这样剥夺你的积分的。(我也没有得到积分,因为我的声望已经超过了2k。) 我不知道你是否会再次尝试编辑,所以我想确保真相被揭示出来 :P 感谢你成为一个细心的读者并且第一时间修复它! - matts
@matts 我认为你重新实现编辑是完全正确的。当我看到好的编辑建议被扭曲地拒绝时,我经常这样做 - 但是在我的编辑消息中,我喜欢链接回原始建议的编辑。这至少给了建议者一些信用 - 如果他们以后返回帖子 - 让他们知道有人欣赏他们的工作,并认识到他们是审核队列愚蠢不公正的受害者。 - Mark Amery
显示剩余4条评论

18

将值转换为布尔形式,然后进行按位异或

Boolean(a) ^ Boolean(b) // === 0 | 1

注意,这个表达式的结果是一个数字,而不是布尔值。
按位异或也适用于非布尔值,但请记住这是一个按位运算符,而不是逻辑运算符。使用非布尔值可能会出现意料之外的结果:
(5 ^ 3) === 6 // true

13

将其转换为布尔值,然后执行异或操作,如 -

!!a ^ !!b

8
请注意,!!a ^ !!b 等同于 !a ^ !b。对于哪个更易读可能存在争议。 - tschwab

12

有一种排序方式可能会更适合你:

if( foo ? !bar : bar ) {
  ...
}

更容易阅读:

if( ( foo && !bar ) || ( !foo && bar ) ) {
  ...
}

为什么?不知道。

因为JavaScript开发者认为它是不必要的,因为它可以通过其他已实现的逻辑运算符来表达。

你也可以选择使用NAND,然后就可以从中推断出所有可能的逻辑运算。

我个人认为,这有历史原因,源自于基于C语法的编程语言,在那里,xor不常见,或者至少极其不常见。


是的,JavaScript 有三元运算符。 - mwilcox
C和Java都使用^(脱字符号)来实现异或运算。 - veidelis

8

是的,只需要按照以下步骤进行操作。 假设您要处理的是布尔值 A 和 B,则可以使用以下 JavaScript 代码来计算 A XOR B 的值:

var xor1 = a !== b;

前一行与以下内容相同

var xor2 = (!a !== !b);

就个人而言,我更喜欢使用xor1,因为我只需要少打几个字符。我认为xor1的速度也更快。它只执行了两个计算。而xor2则执行了三个计算。

可视化解释...请查看下表(其中0代表false1代表true),并比较第3列和第5列。

!(A === B)

| A | B | A XOR B | A === B | !(A === B) |
------------------------------------------
| 0 | 0 |    0    |    1    |      0     |
| 0 | 1 |    1    |    0    |      1     |
| 1 | 0 |    1    |    0    |      1     |
| 1 | 1 |    0    |    1    |      0     |
------------------------------------------

享受吧。


6
"var xor1 = !(a === b);" 和 "var xor1 = a !== b;" 是相同的意思。 - daniel1426
这个答案并不适用于所有的数据类型(就像Premchandra的答案一样)。例如,!(2 === 3)true,但是23是“truthy”的,所以2 XOR 3应该是false - Mariano Desanze
3
如果您更仔细地阅读我的消息,您会注意到我写道:“假设您正在处理布尔值 A 和 B ...”。 - asiby

6

3
嘿,如果JavaScript添加了一个逻辑异或运算符,会使代码示例看起来更清晰。 - Danyal Aytekin

4
将结果int转换为bool,可以使用双重否定吗? 这种方法不够优美,但非常紧凑。

var state1 = false,
    state2 = true;
    
var A = state1 ^ state2;     // will become 1
var B = !!(state1 ^ state2); // will become true
console.log(A);
console.log(B);


如果操作数不是布尔值,这将失败。更好的方法是 B = ((!state1)!==(!state2)) - Doin
真的,但你可以始终否定操作数来转换它们,就像你在不确定类型时所做的那样: B =!!(!state1 ^ !state2);另外,为什么要用这么多括号?B = !state1 !== !state2;或者你甚至可以省略否定:B = state1 !== state2; - Lajos Mészáros
括号是为了清晰明了,也是为了在编写代码时不必检查运算符优先级的文档!;-) 你最后的表达式遭受了我之前的抱怨:如果操作数不是布尔值,则会失败。但是如果你确定它们是布尔值,那么它绝对是最简单和最快速的“逻辑异或”表达式。 - Doin
如果你所说的“last expression”是指state1 !== state2,那么你在那里不需要进行任何转换,因为!==是一个逻辑运算符,而不是位运算符。12 !== 4是真的,'xy' !== true也是真的。如果你使用!=而不是!==,那么你就必须进行转换。 - Lajos Mészáros
顺便说一下,我更喜欢在代码中使用额外的空格而非括号来提升可读性。 - Lajos Mészáros
1
!==!=的结果始终是布尔值...不确定你所做的区分应该是什么,那绝对不是问题所在。问题在于我们想要的异或运算符实际上是表达式(Boolean(state1) !== Boolean(state2))。对于布尔值,"xy"、12、4和true都是真值,并且应该转换为true。因此("xy" XOR true)应该是false,但是("xy" !== true)却是true,正如你所指出的那样。因此,!==!=只有在应用之前将它们的参数转换为布尔值时才等同于"逻辑异或"。 - Doin

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