当波浪线在表达式之前时,它有什么作用?

234
var attr = ~'input,textarea'.indexOf( target.tagName.toLowerCase() )
           ? 'value'
           : 'innerHTML'

我在一个答案中看到了它,但以前从未见过。

这是什么意思?


5个回答

317

~ 是一种位运算符,它会翻转其操作数中的所有位。

例如,如果你的数字是1,它在IEEE 754浮点数(JavaScript处理数字的方式)中的二进制表示为...

0011 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

因此,~将其操作数转换为32位整数(JavaScript中的按位运算符执行此操作)...

0000 0000 0000 0000 0000 0000 0000 0001

如果它是一个负数,它将以二进制补码存储:反转所有位并加1。...然后翻转所有位...
1111 1111 1111 1111 1111 1111 1111 1110

那么它有什么用呢?什么时候会用到它?它有很多用途。如果你编写低级别的东西,它非常方便。如果您分析了应用程序并发现了瓶颈,则可以使用位操作技巧(作为更大工具包中的一种可能工具)使其更高效。它也是将indexOf()的找到返回值转换为truthy(同时将未找到的转换为falsy)的不明确的技巧,并且人们经常使用它来截断数字为32位(通过加倍其十进制部分来有效地删除其小数点,与正数的Math.floor()相同)。我说不明确,因为它的用途不是立即显而易见的。通常情况下,您希望您的代码对其他人阅读清晰明了。虽然使用~可能看起来很酷,但通常过于聪明反被聪明误。:)

现在 JavaScript 已经有 Array.prototype.includes()String.prototype.includes(),所以这不再那么重要了。它们返回一个布尔值。如果你的目标平台支持它们,你应该优先使用它们来测试字符串或数组中是否存在某个值。


4
“nasty”这个词用得是否得当?如果确实得当,我会把它视为该语言的习语。有很多种习语,一旦你学会了它们,它们就不会含糊不清。如果你不熟悉 Python 的列表推导式,它们可能不太容易理解,但可以使用更冗长的循环来完成,但您永远不会要求Python程序员不使用它们。同样地,在 JavaScript 中, value = value || default 是一种常见且有效的习惯用法,只要你知道什么时候能够使用它和不能使用它。 - gman
4
@gman 我想,是否有人使用并不是很重要。我认为将列表推导式(语言特性)与此进行比较并不是一回事(一种聪明的方式避免输入一些额外的字符)。如果你认为“nasty”这个词太过严厉,请随意编辑我的答案。 - alex
2
也许更普遍的例子是v = t ? a : b;。我发现这种写法比通常分成5行以上的var v; if (t} { v = a; } else { v = b; }清晰得多,也比通常要占用4行以上的var v = b; if (t) { v = a; }清晰。但是,我知道很多不熟悉? :运算符的人会更喜欢第二或第三种写法。我发现第一种写法更易读。 我同意这个一般原则,即让代码清晰,不用hack。我想我只是觉得一旦我学会了~v.indexOf('...'),就非常清晰。 - gman
14
当你在一个有很多开发者的大型公司工作时,你希望代码能够清晰写出并且有良好的文档。使用具有垃圾回收功能的高级编程语言的目的是避免考虑二进制操作,而且许多前端开发人员甚至没有汇编语言的经验。 - user2867288
4
我不会称~是惯用语。它在技术上是语言规范的一部分,但并不是语言在普遍使用中的一部分。 - worc
显示剩余6条评论

138

indexOf() 表达式之前使用它,可以有效地将直接返回的数字索引转换为真值/假值结果。

如果返回值是 -1,那么 ~-1 就是 0,因为 -1 是所有 1 位组成的字符串。任何大于或等于零的值都会给出非零结果。因此,

if (~someString.indexOf(something)) {
}

当"something"在"someString"中时,将会触发if代码的运行。 如果您直接使用.indexOf()作为布尔值,则无法正常工作,因为有时它返回零(当"something"位于字符串开头时)。

当然,这也可以实现:

if (someString.indexOf(something) >= 0) {
}

并且这要少得多神秘。

有时你也会看到这个:

var i = ~~something;

使用两次~运算符是将字符串快速转换为32位整数的一种方法。第一个~执行转换,第二个~将位反转回来。当然,如果该运算符应用于无法转换为数字的内容,则会得到NaN作为结果。(编辑 — 其实是先应用第二个~,但你明白我的意思。)


2
对于那些不想逐位取反的人来说,整数上执行的~等同于-(x + 1) - Fabrício Matté
8
“@adlwalrus,‘0’代表‘假’,非零则代表‘真’的传统可以追溯到上世纪七十年代的C语言,以及许多当时流行的系统编程语言。这可能源于硬件运作的方式;许多CPU在操作后会设置一个零位,并有相应的分支指令进行测试。” - Pointy
4
将其快速转换为32位整数的方法是| 0,这种情况下只需要进行一次操作即可。 - alex
@alex 的确如此,尽管我们不能完全信任运行时不会以完全相同的方式解释对 ~~ 的简单应用。 - Pointy
@wwaawaw,"负布尔值"这种东西是不存在的,布尔类型只有 1 或 0,除非你说的是负二进制值... - Hejazzman
显示剩余4条评论

40

~按位非运算符~x 大致相当于 -(x+1)。这更容易理解,有点类似于负数。因此:

~2;    // -(2+1) ==> -3
考虑-(x+1)-1 可以使用该操作来生成0
换句话说,将~与一定范围的数字值一起使用仅会对输入值为-1的情况生成false(从0强制转换为false),否则它将生成任何其他truthy值。
正如我们所知,-1通常被称为“哨兵值”。在C语言中,许多返回成功>= 0值和失败-1值的函数都使用它。这与JavaScript中indexOf()的返回值规则相同。
以这种方式检查一个字符串中是否存在/不存在子字符串是很常见的。
var a = "Hello Baby";

if (a.indexOf("Ba") >= 0) {
    // found it
}
if (a.indexOf("Ba") != -1) { 
    // found it
}

if (a.indexOf("aB") < 0) { 
    // not found
}
if (a.indexOf( "aB" ) == -1) { 
    // not found
}

然而,以下使用~来实现会更容易。

var a = "Hello Baby";

~a.indexOf("Ba");         // -7   -> truthy
if (~a.indexOf("Ba")) {   // true
    // found it
}

~a.indexOf("aB");         // 0    -> falsy
!~a.indexOf("aB");        // true
if (!~a.indexOf( "aB" )) {  // true
    // not found
}

你不知道的JavaScript:类型和语法,作者Kyle Simpson


1
它在表面上的理解绝对更容易,即使一个人不明白其背后的工作原理。如果我在if语句中看到-(x+1),我会再看一眼。波浪线告诉我它正是为了弥补Javascript的基于0的本质而做的。此外,阅读时括号越少越好。 - Regular Jo
在您的初始检查代码块中,您可以使用if (a.indexOf("Ba") > -1) {// found} //true来少打点字。尽管比波浪线示例略长,但比您给出的两个示例要短得多,并且对于新程序员来说易于理解 var opinion = ! ~-1 ? 'more' : 'less' - Hmerman6006

29

~indexOf(item) 经常被提及,这里的答案很棒,但也许有些人只需要知道如何使用它并“跳过”理论:


   if (~list.indexOf(item)) {
     // item in list
   } else {
     // item *not* in list
   }

2
我同意。Airbnb JavaScript风格指南不允许使用++--,因为它们“鼓励过度的巧妙性”,但一些符号如~却幸存了下来(潜伏在阴影中)。https://github.com/airbnb/javascript/issues/540 - Shanimal
@Shanimal 另一种选择是 list.indexOf(item) >= 0... > -1,因为 JavaScript 是从零开始的,并没有从一开始就解决这个问题。此外,只是个人意见(与 Airbnb 相同),任何在 JavaScript 中进行有意义操作的人都知道 ++,而虽然 -- 不太常见,但含义可以推断出来。 - Regular Jo
@RegularJoe 我大部分都同意。个人而言,由于像mapforEach等原始方法的存在,我已经有一段时间没有使用++--了。我的观点更多地是关于为什么他们没有考虑到~也过于棘手,因为无论使用哪种标准,都包括增量和减量运算符。禁止某些东西以便让CIS101变得毫无意义。 - Shanimal

16

对于那些考虑使用波浪线技巧从indexOf结果创建一个真值的人,更明确且有更少魔法的方法是改用String上的includes方法

'hello world'.includes('hello') //=> true
'hello world'.includes('kittens') //=> false

请注意,这是ES 2015以后的新标准方法,所以在旧浏览器上无法使用。如果涉及到这种情况,请考虑使用String.prototype.includes polyfill

同样的语法也适用于数组,具体请参见此处

['apples', 'oranges', 'cherries'].includes('apples') //=> true
['apples', 'oranges', 'cherries'].includes('unicorns') //=> false

如果您需要支持旧浏览器,这里是 Array.prototype.includes polyfill


2
避免使用includes()。在撰写本文时,它在任何版本的IE中都不受支持(不仅仅是旧版浏览器):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes - Onshop
11
可以使用编译器来编写更清晰的代码,而不是按照目前最差的JS解释器的标准来编写代码。请注意保持内容原意并简洁易懂,不提供其他信息。 - Kyle Baker
7
@Ben是正确的,它在Netscape 4.72中也不起作用。 - mikemaccana
1
@Onshop,我认为真正需要避免的是不支持标准Javascript的IE浏览器。 - Константин Ван

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