Как !! ~ (не тильда / взрыва тильда) изменяет результат вызова метода массива "содержит / включен"?

95

Если вы читаете комментарии на inArrayстранице jQuery здесь , есть интересное объявление:

!!~jQuery.inArray(elm, arr) 

Теперь я считаю, что двойной восклицательный знак преобразует результат в тип booleanсо значением true. Чего я не понимаю, так это то, что во всем этом используется ~оператор тильда ( )?

var arr = ["one", "two", "three"];
if (jQuery.inArray("one", arr) > -1) { alert("Found"); }

Рефакторинг ifзаявления:

if (!!~jQuery.inArray("one", arr)) { alert("Found"); }

Сломать:

jQuery.inArray("one", arr)     // 0
~jQuery.inArray("one", arr)    // -1 (why?)
!~jQuery.inArray("one", arr)   // false
!!~jQuery.inArray("one", arr)  // true

Еще я заметил, что если поставить тильду впереди, то результат будет -2.

~!!~jQuery.inArray("one", arr) // -2

Я не понимаю, зачем здесь тильда. Может кто-нибудь объяснить это или указать мне на ресурс?

user717236
источник
50
Тот, кто напишет такой код, должен отойти от клавиатуры.
Кирк Уолл
12
@KirkWoll: Почему? ~jQuery.inArray()на самом деле очень полезно - возможно, даже очень хорошая причина, по которой функции поиска возвращают -1ошибку (единственное значение, дополненное двумя значениями, является ложным). Как только вы увидели и поняли трюк, я почувствовал, что он даже более читабелен, чем != -1.
Амадан
9
@ Амадан - нет. Просто нет. Серьезно, я не могу поверить, что ты защищаешься ни!!~ за что .
Кирк Уолл
24
Проблема в том, что это просто "уловка". Основное различие между if (x != -1)и if (~x)для меня в том, что первое на самом деле выражает то, что вы собираетесь делать. Последнее выражает ваше желание сделать что-то совершенно другое («пожалуйста, преобразуйте мой 64-битный номер в 32-битное целое и проверьте, истинно ли побитовое НЕ этого целого числа»), где вы просто случайно получили желаемый результат в этом один случай.
JimmiTh
10
>= 0вероятно , не Лит достаточно, поэтому более загадочным !!~был использован.
Йоши

Ответы:

56

Оператор тильды на самом деле вообще не является частью jQuery - это побитовый оператор НЕ в самом JavaScript.

См . Великую Тайну Тильды (~) .

Вы получаете странные числа в своих экспериментах, потому что вы выполняете поразрядную логическую операцию с целым числом (которое, насколько я знаю, может быть сохранено как дополнение до двух или что-то в этом роде ...)

Дополнение до двух объясняет, как представить число в двоичном формате. Думаю, я был прав.

Pglhall
источник
3
Исправлена! (Изменил ее на другую ссылку, которая, как ни странно, была написана после моего первоначального ответа ...)
pglhall
121

Есть конкретная причина, по которой вы иногда можете увидеть ~примененную перед$.inArray .

В принципе,

~$.inArray("foo", bar)

это более короткий способ сделать

$.inArray("foo", bar) !== -1

$.inArrayвозвращает индекс элемента в массиве, если первый аргумент найден, и возвращает -1, если он не найден. Это означает, что если вы ищете логическое значение «находится ли это значение в массиве?», Вы не можете выполнить логическое сравнение, поскольку -1 - это истинное значение, а когда $ .inArray возвращает 0 (ложное значение ), это означает, что он действительно находится в первом элементе массива.

Применение ~побитового оператора приводит -1к тому, что становится 0, а 0 становится `-1. Таким образом, не нахождение значения в массиве и применение побитового НЕ приводит к ложному значению (0), а все остальные значения возвращают числа, отличные от 0, и будут представлять правдивый результат.

if (~$.inArray("foo", ["foo",2,3])) {
    // Will run
}

И это будет работать, как задумано.

Yahel
источник
2
Насколько хорошо это поддерживается в браузерах (сейчас в 2014 году?) Или все время поддерживалось идеально?
Explosion Pills
я был бы удивлен, если бы такие базовые операции не были идеальными.
pcarvalho
104

!!~exprимеет значение , falseкогда exprв -1противном случае true.
Такой же expr != -1, только битый *


Это работает, потому что побитовые операции JavaScript преобразуют операнды в 32-разрядные целые числа со знаком в формате дополнения до двух. Таким образом !!~-1оценивается следующим образом:

   -1 = 1111 1111 1111 1111 1111 1111 1111 1111b // two's complement representation of -1
  ~-1 = 0000 0000 0000 0000 0000 0000 0000 0000b // ~ is bitwise not (invert all bits)
   !0 = true                                     // ! is logical not (true for falsy)
!true = false                                    // duh

Для значения, отличного от -1, по крайней мере, один бит будет установлен в ноль; инвертирование создаст истинное значение; применение! оператора дважды к истинному значению возвращает логическое значение true.

При использовании с .indexOf()и мы только хотим проверить, соответствует ли результат -1:

!!~"abc".indexOf("d") // indexOf() returns -1, the expression evaluates to false
!!~"abc".indexOf("a") // indexOf() returns  0, the expression evaluates to true
!!~"abc".indexOf("b") // indexOf() returns  1, the expression evaluates to true

* !!~8589934591 оценивается как ложь, поэтому этомерзостьне может быть надежно использован для проверки -1.

Салман А
источник
1
В стабильной библиотеке я не вижу проблем с использованием ~foo.indexOf(bar), это не означает значительной экономии символов или производительности, но это относительно распространенное сокращение, как и foo = foo || {}есть.
zzzzBov
6
Это не проблема ... по крайней мере, пока кого-то еще не попросят продолжить работу над вашим кодом.
Salman A
9
@zzzzBov, чтобы расширить комментарий Салмана : всегда
ahsteele
1
@ahsteele, я хорошо знаю это правило, однако побитовые операторы являются частью каждого языка программирования, о котором я могу думать. Я стараюсь программировать таким образом, чтобы его мог читать тот, кто умеет читать код . Я не прекращаю использовать особенности языка просто потому, что кто-то его не понимает, иначе я бы даже не смог их использовать!! .
zzzzBov
Строго говоря, >= 0не ведет себя так же, как !!~. !== -1ближе.
Питер Олсон,
33

~foo.indexOf(bar)- это обычное сокращение для обозначения, foo.contains(bar)потому что containsфункция не существует.

Обычно преобразование в логическое значение не требуется из-за концепции JavaScript о «ложных» значениях. В этом случае он используется для принудительного вывода функции на trueили false.

zzzzBov
источник
6
+1 Этот ответ объясняет «почему» лучше, чем принятый ответ.
nalply
18

jQuery.inArray()возвращает значение -1«не найдено», для которого используется метод complement ( ~) 0. Таким образом, ~jQuery.inArray()возвращает ложное значение ( 0) для «не найдено» и истинное значение (отрицательное целое число) для «найдено». !!затем формализует ложь / истину в реальное логическое значение false/ true. Итак, !!~jQuery.inArray()отдам trueза «найдено» и falseза «не найдено».

Амадан
источник
13

Для ~всех 4 байтов intэта формула равна-(N+1)

ТАК

~0   = -(0+1)   // -1
~35  = -(35+1)  // -36 
~-35 = -(-35+1) //34 
Мина Габриэль
источник
3
Это не всегда так, поскольку (например) ~2147483648 != -(2147483648 + 1).
Frxstrem
10

~Является оператором побитового дополнения. Целочисленный результат inArray()- либо -1, если элемент не найден, либо некоторое неотрицательное целое число. Поразрядное дополнение до -1 (представленное в двоичном виде как все 1 бит) равно нулю. Поразрядное дополнение любого неотрицательного целого числа всегда не равно нулю.

Таким образом, !!~iбудет, trueкогда целое число «i» является неотрицательным целым числом, а falseкогда «i» равно -1.

Обратите внимание, что ~всегда приводит свой операнд к целому числу; то есть, он переводит нецелые значения с плавающей запятой в целые, а также нечисловые значения.

Заостренный
источник
10

Тильда - это побитовое НЕ - она ​​инвертирует каждый бит значения. Как правило, если вы используете ~число, его знак будет инвертирован, а затем будет вычтена 1.

Таким образом, когда вы это сделаете ~0, вы получите -1 (инвертированный 0 равен -0, вычитание 1 равно -1).

По сути, это тщательно продуманный, сверхмикрооптимизированный способ получения значения, которое всегда является логическим.

Джо
источник
8

Вы правы: этот код вернется, falseкогда indexOfвызов вернет -1; иначе true.

Как вы говорите, гораздо разумнее было бы использовать что-то вроде

return this.modifiedPaths.indexOf(path) !== -1;
Лука
источник
1
Но это еще 3 байта для отправки клиенту! edit: (кстати, просто шутя, опубликовал мой комментарий и понял, что это не очевидно (что и грустно, и глупо))
Уэсли Мерч
@Wesley: Это правда, но он должен быть отправлен каждому клиенту только один раз , если клиент кэширует файл .js. Сказав это, они могли бы использовать >=0вместо !==-1- никаких дополнительных байтов для отправки и еще более читабельны, чем версия с битовым тиддлингом.
LukeH
2
Кто здесь кого троллит? ;) Думаю, я считал само собой разумеющимся, что писать читаемый код лучше, чем загадочный предварительно оптимизированный код, который генерирует подобные вопросы. Просто минимизируйте позже и напишите читаемый и понятный код сейчас.
Уэсли Мерч,
2
Лично я бы сказал, что > -1это даже более читабельно, но, вероятно, это очень субъективно.
Йоши
6

~Оператор побитового НЕ оператор. Это означает, что он принимает число в двоичной форме и превращает все нули в единицы и единицы в нули.

Например, число 0 в двоичном формате равно 0000000, а число -1 11111111. Точно так же 1 00000001в двоичном формате, а -2 11111110.

Frxstrem
источник
3

Я предполагаю, что он там, потому что он на несколько символов короче (какие авторы библиотеки всегда после). Он также использует операции, которые занимают всего несколько машинных циклов при компиляции в собственный код (в отличие от сравнения с числом).

Я согласен с другим ответом, что это излишество, но, возможно, имеет смысл в жестком цикле (однако требуется оценка прироста производительности, иначе может оказаться преждевременной оптимизацией).

Александр Павлов
источник
2

Я полагаю, поскольку это побитовая операция, это самый быстрый (дешевый в вычислительном отношении) способ проверить, отображается ли путь в modifiedPaths.

panos2point0
источник
1

Как (~(-1)) === 0, так:

!!(~(-1)) === Boolean(~(-1)) === Boolean(0) === false
Инженер
источник
1
Возможно, это и верно, но полезно ли это объяснение для спрашивающего? Не за что. Если бы я не понял этого с самого начала, такой краткий ответ не помог бы.
Spudley
Думаю, этот ответ имеет смысл. Если у вас математический мозг, вы можете четко видеть, какие части меняются на каждом этапе. Это лучший ответ на этот вопрос? Нет. Но это полезно, я так считаю! +1
Тейлор Лопес