Como !! ~ (não til / bang bang til) altera o resultado de uma chamada de método Array 'contém / incluído'?

95

Se você leu os comentários na inArraypágina jQuery aqui , há uma declaração interessante:

!!~jQuery.inArray(elm, arr) 

Agora, acredito que um ponto de exclamação duplo converterá o resultado em tipo boolean, com o valor de true. O que eu não entendo é qual é o uso do ~operador til ( ) em tudo isso?

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

Refatorando a ifdeclaração:

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

Demolir:

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

Também notei que se eu colocar o til na frente, o resultado é -2.

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

Não entendo o propósito do til aqui. Alguém pode me explicar ou indicar um recurso?

user717236
fonte
50
Quem quer que queira escrever um código como esse precisa se afastar do teclado.
Kirk Woll
12
@KirkWoll: Por quê? ~jQuery.inArray()é realmente muito útil - possivelmente até mesmo um bom motivo pelo qual as funções de pesquisa retornam -1para falha (o único valor cujo complemento de dois é falso). Depois de ver e entender o truque, sinto que é ainda mais legível do que != -1.
Amadan de
9
@Amadan - não. Apenas não. Sério, eu não acredito que você está defendendo !!~para nada .
Kirk Woll de
24
O problema é que é só isso: um "truque". A principal diferença entre if (x != -1)e if (~x)para mim é que o primeiro realmente expressa o que você pretende fazer. Este último expressa que você deseja fazer algo totalmente diferente ("por favor, converta meu número de 64 bits em um inteiro de 32 bits e verifique se o NÃO bit a bit desse inteiro é verdadeiro"), onde você obtém o resultado desejado neste Um caso.
JimmiTh
10
>= 0provavelmente não era pequeno o suficiente, então o mais enigmático !!~foi usado.
Yoshi

Respostas:

56

O operador til não faz parte do jQuery - é um operador NOT bit a bit no próprio JavaScript.

Veja O Grande Mistério do Til (~) .

Você está recebendo números estranhos em seus experimentos porque está realizando uma operação lógica bit a bit em um número inteiro (que, pelo que sei, pode ser armazenado como complemento de dois ou algo parecido ...)

O complemento de dois explica como representar um número em binário. Eu acho que estava certo.

pglhall
fonte
3
Fixo! (Alterado para outro link que, bizarramente, foi escrito após minha resposta original ...)
pglhall
121

Há um motivo específico que às vezes você verá ~aplicado antes $.inArray.

Basicamente,

~$.inArray("foo", bar)

é uma maneira mais curta de fazer

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

$.inArrayretorna o índice do item na matriz se o primeiro argumento for encontrado e retorna -1 se não for encontrado. Isso significa que se você estiver procurando por um booleano de "este valor está no array?", Você não pode fazer uma comparação booleana, pois -1 é um valor verdadeiro e quando $ .inArray retorna 0 (um valor falso ), significa que ele realmente foi encontrado no primeiro elemento da matriz.

A aplicação do ~operador bit a bit -1torna 0e faz com que 0 se torne `-1. Portanto, não encontrar o valor na matriz e aplicar o NOT bit a bit resulta em um valor falso (0), e todos os outros valores retornarão números diferentes de 0 e representarão um resultado verdadeiro.

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

E funcionará conforme o planejado.

Yahel
fonte
2
Quão bem é suportado em navegadores (agora em 2014?) Ou foi perfeitamente suportado o tempo todo?
Pílulas de explosão
Eu ficaria surpreso se operações básicas como essas não fossem perfeitas.
pcarvalho
104

!!~expravalia falsequando expré -1diferente true.
É o mesmo que expr != -1, apenas quebrado *


Funciona porque as operações bit a bit do JavaScript convertem os operandos em inteiros assinados de 32 bits no formato de complemento de dois. Assim, !!~-1é avaliado da seguinte forma:

   -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

Um valor diferente de -1terá pelo menos um bit definido como zero; invertê-lo criará um valor verdadeiro; aplicar o !operador duas vezes a um valor verdadeiro retorna verdadeiro booleano.

Quando usado com .indexOf()e queremos apenas verificar se o resultado é -1ou não:

!!~"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

* !!~8589934591avalia como falso, então esteabominaçãonão pode ser usado de forma confiável para testar -1.

Salman A
fonte
1
Em uma biblioteca estável, não vejo nenhum problema com o uso ~foo.indexOf(bar), não é uma economia significativa em personagens ou desempenho, mas é um atalho relativamente comum da mesma maneira que foo = foo || {}é.
zzzzBov
6
Não é um problema ... não pelo menos até que alguém seja solicitado a continuar com seu código.
Salman A
1
@ahsteele, estou bem ciente dessa regra, no entanto, os operadores bit a bit fazem parte de todas as linguagens de programação que consigo pensar. Tento programar de uma forma que seja legível para alguém que saiba ler código . Não paro de usar recursos de uma linguagem simplesmente porque outra pessoa não entende, caso contrário, eu nem conseguiria usar!! .
zzzzBov
A rigor, >= 0não tem o mesmo comportamento que !!~. !== -1está mais próxima.
Peter Olson
33

~foo.indexOf(bar)é uma abreviatura comum de representar foo.contains(bar)porque a containsfunção não existe.

Normalmente, a conversão para booleano é desnecessária devido ao conceito de valores "falsos" do JavaScript. Nesse caso, é usado para forçar a saída da função a ser trueou false.

zzzzBov
fonte
6
+1 Esta resposta explica o "porquê" melhor do que a resposta aceita.
nalply
18

jQuery.inArray()retorna -1para "não encontrado", cujo complemento ( ~) é 0. Portanto, ~jQuery.inArray()retorna um valor falso ( 0) para "não encontrado" e um valor verdadeiro (um número inteiro negativo) para "encontrado". !!irá então formalizar o falso / verdadeiro em booleano real false/ true. Então, !!~jQuery.inArray()dará truepara "encontrado" e falsepara "não encontrado".

Amadan
fonte
13

O ~para todos os 4 bytes inté igual a esta fórmula-(N+1)

TÃO

~0   = -(0+1)   // -1
~35  = -(35+1)  // -36 
~-35 = -(-35+1) //34 
Mina Gabriel
fonte
3
Isso nem sempre é verdade, pois (por exemplo) ~2147483648 != -(2147483648 + 1).
Frxstrem
10

O ~operador é o operador de complemento bit a bit. O resultado inteiro de inArray()é -1, quando o elemento não é encontrado, ou algum inteiro não negativo. O complemento bit a bit de -1 (representado em binário como todos os bits 1) é zero. O complemento bit a bit de qualquer inteiro não negativo é sempre diferente de zero.

Assim, !!~iserá truequando o inteiro "i" for um inteiro não negativo e falsequando "i" for exatamente -1.

Observe que ~sempre coage seu operando para inteiro; isto é, ele força valores de ponto flutuante não inteiros para inteiros, bem como valores não numéricos.

Pontudo
fonte
10

Til NÃO é bit a bit - ele inverte cada bit do valor. Como regra geral, se você usar ~em um número, seu sinal será invertido e, em seguida, 1 será subtraído.

Portanto, ao fazer isso ~0, você obtém -1 (0 invertido é -0, subtrair 1 é -1).

É essencialmente uma maneira elaborada e supermicrotimizada de obter um valor que é sempre booleano.

Joe
fonte
8

Você está certo: Este código retornará falsequando a indexOfchamada retornar -1; caso contrário true.

Como você disse, seria muito mais sensato usar algo como

return this.modifiedPaths.indexOf(path) !== -1;
LukeH
fonte
1
Mas são mais 3 bytes para enviar ao cliente! editar: (só brincando, postei meu comentário e percebi que não era óbvio (o que é tanto triste quanto bobo))
Wesley Murch
@Wesley: Isso é verdade, mas só precisa ser enviado a cada cliente uma vez , supondo que o cliente armazenará em cache o .js. Dito isso, eles poderiam usar >=0ao invés de !==-1- nenhum byte extra para enviar e ainda mais legível do que a versão bit-twiddling.
LukeH
2
Quem está trollando quem aqui? ;) Acho que presumi que escrever código legível é melhor do que código críptico pré-otimizado que gera esse tipo de pergunta. Basta minimizar mais tarde e escrever um código legível e compreensível agora.
Wesley Murch
2
Pessoalmente, diria que > -1é ainda mais legível, mas provavelmente muito subjetivo.
Yoshi
6

O ~operador é o operador NOT bit a bit. O que isso significa é que ele pega um número na forma binária e transforma todos os zeros em uns e os uns em zeros.

Por exemplo, o número 0 em binário é 0000000, enquanto -1 é 11111111. Da mesma forma, 1 está 00000001em binário, enquanto -2 está 11111110.

Frxstrem
fonte
3

Meu palpite é que está lá porque tem alguns caracteres mais curtos (o que os autores da biblioteca sempre buscam). Ele também usa operações que levam apenas alguns ciclos de máquina quando compiladas no código nativo (em oposição à comparação com um número).

Concordo com outra resposta de que é um exagero, mas talvez possa fazer sentido em um ciclo restrito (requer estimativa de ganho de desempenho, caso contrário, pode acabar sendo uma otimização prematura).

Alexander Pavlov
fonte
2

Suponho que, por ser uma operação bit a bit, é a maneira mais rápida (computacionalmente barata) de verificar se o caminho aparece emmodedPaths.

panos2point0
fonte
1

Como (~(-1)) === 0, então:

!!(~(-1)) === Boolean(~(-1)) === Boolean(0) === false
Engenheiro
fonte
1
Isso pode ser preciso, mas é uma explicação útil para o questionador? De modo nenhum. Se eu não entendesse para começar, uma resposta concisa como essa não ajudaria.
Spudley de
Eu acho que essa resposta faz sentido. Se você tem um cérebro matemático, pode ver claramente quais partes estão mudando a cada etapa. É a melhor resposta para essa pergunta? Não. Mas é útil, acho que sim! +1
Taylor Lopez