O que um til faz quando precede uma expressão?

Respostas:

267

~é um operador bit a bit que inverte todos os bits em seu operando.

Por exemplo, se seu número fosse 1, sua representação binária do flutuador IEEE 754 (como JavaScript trata os números) seria ...

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

Portanto, ~converte seu operando em um número inteiro de 32 bits (operadores bit a bit em JavaScript fazem isso) ...

0000 0000 0000 0000 0000 0000 0000 0001

Se fosse um número negativo, seria armazenado no complemento de 2: inverta todos os bits e adicione 1.

... e depois vira todos os seus bits ...

1111 1111 1111 1111 1111 1111 1111 1110

Então, qual é a utilidade disso? Quando alguém poderia usá-lo?

Tem alguns usos. Se você está escrevendo coisas de baixo nível, é útil. Se você definiu o perfil do seu aplicativo e encontrou um gargalo, ele poderia ter um desempenho melhor usando truques bit a bit (como uma ferramenta possível em um pacote muito maior).

É também uma (geralmente) claro truque para transformar indexOf()'s encontrados valor de retorno em truthy (ao fazer não encontrado como Falsas ) e muitas vezes as pessoas usá-lo para seu efeito colateral de truncar números para 32 bits (e soltando sua casa decimal, duplicando-a, efetivamente o mesmo que Math.floor()para números positivos).

Digo claro porque não é imediatamente óbvio para o que está sendo usado. Geralmente, você deseja que seu código se comunique claramente com outras pessoas que o leem. Embora o uso ~possa parecer legal , geralmente é inteligente demais para o seu próprio bem. :)

Também é menos relevante agora que JavaScript Array.prototype.includes()e String.prototype.includes(). Eles retornam um valor booleano. Se a (s) sua (s) plataforma (s) de destino o suportarem, você deve preferir isso para testar a existência de um valor em uma string ou matriz.

alex
fonte
2
Desagradável é a palavra certa? Se funcionar, eu chamaria isso de idioma da linguagem. Há muitos idiomas. Depois de aprendê-los, eles não são claros. A compreensão da lista não é clara no Python se você não os conhece e pode ser realizada com loops mais detalhados, mas você nunca pediria a um programador do Python que não os usasse. Da mesma forma value = value || default, o JavaScript é um idioma comum e válido, desde que você saiba quando pode e não pode usá-lo.
Gman #
3
@ gman Eu acho que realmente não importa se alguém usa ou não. Eu acho que comparar a compreensão da lista (recurso de idioma) com isso não é a mesma coisa ( maneira inteligente de evitar a digitação de alguns caracteres extras). Se você acha desagradável um termo muito severo, sinta-se à vontade para editar minha resposta.
1628 alex
2
Talvez um exemplo mais comum seja v = t ? a : b;. Eu acho isso muito mais claro do que var v; if (t} { v = a; } else { v = b; }normalmente dividido em mais de 5 linhas e também mais claro do var v = b; if (t) { v = a; }que o que normalmente seria 4+ linhas. Mas conheço muitas pessoas não familiarizadas com os ? :operadores que preferem a segunda ou terceira via. Acho que o primeiro é mais legível. Eu concordo com o princípio geral, deixe o código claro, não use hacks. Eu acho que só fico ~v.indexOf('...')muito claro depois de aprender.
Gman #
9
Quando você trabalha em uma grande empresa com muitos desenvolvedores, deseja que o código seja claramente escrito (em inglês) e bem documentado. O objetivo da codificação de alto nível em um idioma com coleta de lixo é evitar pensar em operações binárias, e muitos desenvolvedores de front-end nem sequer têm experiência em linguagem assembly sob seus cintos.
user2867288
3
eu não chamaria de ~idiomático. é tecnicamente parte das especificações do idioma , mas não faz parte do idioma em uso geral .
WORC
108

Usá-lo antes de uma indexOf()expressão efetivamente fornece um resultado de verdade / falsidade em vez do índice numérico retornado diretamente.

Se o valor de retorno for -1, então ~-1é 0porque-1 é uma sequência de todos os 1 bits. Qualquer valor maior ou igual a zero dará um resultado diferente de zero. Portanto,

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

causará o if código seja executado quando "algo" estiver em "someString". Se você tentar usar .indexOf()como um booleano diretamente, isso não funcionará porque, às vezes, retorna zero (quando "algo" está no início da string).

Obviamente, isso também funciona:

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

e é consideravelmente menos misterioso.

Às vezes, você também verá isso:

var i = ~~something;

Usar o ~operador duas vezes como esse é uma maneira rápida de converter uma string em um número inteiro de 32 bits. O primeiro ~faz a conversão e o segundo ~inverte os bits. Obviamente, se o operador for aplicado a algo que não pode ser convertido em um número, você obtém o NaNresultado. ( editar - na verdade, é o segundo ~que é aplicado primeiro, mas você entendeu.)

Pontudo
fonte
2
Para quem não quer negar pouco a pouco, ~quando executado em números inteiros é igual a -(x + 1).
Fabrício Matté 6/09/12
Parece que, bem, você sabe, valores numéricos NEGATIVOS devem retornar valores booleanos negativos também. Mas apenas mais um dos JS 'falha, eu acho?
wwaawaw
7
@adlwalrus bem, a tradição de 0ser falsee ser diferente de zero trueremonta há muito tempo, pelo menos a C nos anos 70 e provavelmente muitas outras linguagens de programação de sistemas então contemporâneas. Provavelmente decorre da maneira como o hardware funciona; Muitas CPUs definem um bit zero após uma operação e possuem uma instrução de ramificação correspondente para testá-lo.
Pointy
4
Uma maneira mais rápida de convertê-lo para um int de 32 bits seria | 0, nesse caso, sua única operação.
alex
@alex de fato, embora não possamos necessariamente confiar no tempo de execução para não interpretar um aplicativo simples da ~~mesma maneira.
Pointy
29

O operador~ é Bitwise NOT , ~xé aproximadamente o mesmo que -(x+1). É mais fácil entender, mais ou menos. Assim:

~2;    // -(2+1) ==> -3

Considere -(x+1). -1pode executar essa operação para produzir a 0.

Em outras palavras, o ~uso com um intervalo de valores numéricos produzirá um valor de falsificação (coagir a falsepartir de 0) apenas para o -1valor de entrada; caso contrário, qualquer outro valor de verdade.

Como sabemos, -1é comumente chamado de valor sentinela . Ele é usado para muitas funções que retornam >= 0valores para o sucesso e -1para a falha na linguagem C. Qual a mesma regra de retorno indexOf()em JavaScript.

É comum verificar a presença / ausência de uma substring em outra string dessa maneira

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
}

No entanto, seria mais fácil fazê-lo ~como abaixo

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
}

Você não conhece JS: Tipos e gramática por Kyle Simpson

zangw
fonte
1
É definitivamente mais fácil entender pelo valor de face, mesmo que uma pessoa não entenda o contexto que a faz funcionar. Eu daria uma segunda olhada -(x+1)se a vi em uma declaração if. O til me diz exatamente o que está fazendo para compensar a natureza baseada em 0 do Javascript. Além disso, quanto menos parênteses, melhor para a leitura
Regular Joe
No seu bloco de código de verificação inicial, você pode digitar menos usando o if (a.indexOf("Ba") > -1) {// found} //truequal, embora um pouco mais longo que os exemplos til, é consideravelmente menor do que os dois exemplos que você deu e é var opinion = !~-1 ? 'more' : 'less'compreensível para novos programadores .
HalfMen
24

~indexOf(item) aparece com bastante frequência, e as respostas aqui são ótimas, mas talvez algumas pessoas precisem saber como usá-lo e "pular" a teoria:

   if (~list.indexOf(item)) {
     // item in list
   } else {
     // item *not* in list
   }
Jorge Bucaran
fonte
1
Eu concordo. Não permite a Guia Airbnb JavaScript Estilo ++e --porque eles "incentivar trickiness excessiva" e ainda de alguma forma ~sobreviveram (à espreita nas sombras) github.com/airbnb/javascript/issues/540
Shanimal
@ Shanimal A alternativa é list.indexOf(item) >= 0ou ... > -1já que o javascript é baseado em zero e não optou por resolver isso desde o início. Além disso, apenas a opinião (igual à do Airbnb), qualquer pessoa que esteja fazendo algo significativo em javascript sabe ++e, embora --seja menos comum, o significado pode ser inferido.
Joe Regular
@RegularJoe Concordo com a maior parte disso. Eu, pessoalmente, não precisava ++e --em um tempo por causa de métodos primitivos como map, forEachetc. Meu ponto é mais sobre por que não também considerar ~excessivamente complicado quando tudo o padrão usado inclui incremento e operadores decréscimo. Proibir algo para que o CIS101 não faça sentido.
Shanimal
11

Para aqueles que consideram usar o truque til para criar um valor verdadeiro a partir de um indexOfresultado, é mais explícito e tem menos mágica para usar o includesmétodoString .

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

Observe que esse é um novo método padrão a partir do ES 2015, portanto não funcionará em navegadores mais antigos. Nos casos em que isso for importante, considere usar o polyfill String.prototype.includes .

Esse recurso também está disponível para matrizes usando a mesma sintaxe :

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

Aqui está o polyfill Array.prototype.includes, se você precisar de suporte mais antigo ao navegador.

Dana Woodman
fonte
2
Evite usar includes (). Ele não é suportado em nenhuma versão do IE (e não apenas em navegadores antigos) no momento da escrita: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Onshop
8
Ou simplesmente usar um compilador, assim você pode escrever código mais claro, em vez de escrever o idioma de acordo com a pior JS intérprete denominador comum lá fora ...
Kyle Baker
2
@ Ben está certo, também não funciona no Netscape 4.72.
mikemaccana