Como diminuir minhas declarações condicionais

154

Eu tenho uma declaração condicional muito longa como a seguinte:

if(test.type == 'itema' || test.type == 'itemb' || test.type == 'itemc' || test.type == 'itemd'){
    // do something.
}

Eu queria saber se eu poderia refatorar essa expressão / declaração de uma forma mais concisa.

Alguma idéia de como conseguir isso?

FlyingCat
fonte
23
Você pode colocá-los em uma matriz e usar in?
Jeremy
agora só se alguém poderia verificar qual deles é o mais rápido
Muhammad Umer
3
isso talvez seja um choque para todos, mas o OP tem um claro vencedor em velocidade !!!!!!! Talvez faça com que o navegador otimize muito isso. Resultados: (1) se com ||. (2) switchdeclarações. (3) regex. (4) ~. jsperf.com/if-statements-test-techsin
Muhammad Umer
3
Você também pode estar abordando isso da maneira errada. Nesse caso, esses 4 tipos têm algo em comum. O que é isso? Se levarmos isso para um caso mais extremo, e se precisarmos adicionar mais 10 tipos para corresponder a essa condição. Ou 100? Se houver mais, você provavelmente não consideraria usar esta solução, ou qualquer outra sugerida. Você está vendo uma declaração if grande como essa e acha que é um cheiro de código, o que é um bom sinal. Sua melhor maneira de tornar isso mais conciso seria se você pudesse escrever if (test.your_common_condition). É mais fácil entender neste contexto e mais extensível.
precisa saber é o seguinte

Respostas:

241

Coloque seus valores em uma matriz e verifique se o item está na matriz:

if ([1, 2, 3, 4].includes(test.type)) {
    // Do something
}

Se um navegador que você suporta não possui o Array#includesmétodo, você pode usar esse polyfill .


Breve explicação do ~atalho til:

Atualização: Como agora temos o includesmétodo, não há mais sentido em usar o ~hack. Basta mantê-lo aqui para pessoas interessadas em saber como ele funciona e / ou o encontraram no código de outras pessoas.

Em vez de verificar se o resultado indexOfé >= 0, existe um pequeno atalho:

if ( ~[1, 2, 3, 4].indexOf(test.type) ) {
    // Do something
}

Aqui está o violino: http://jsfiddle.net/HYJvK/

Como é que isso funciona? Se um item for encontrado na matriz, indexOfretornará seu índice. Se o item não foi encontrado, ele retornará -1. Sem entrar em muitos detalhes, ~é um operador NOT bit bit , que retornará 0apenas para -1.

Eu gosto de usar o ~atalho, pois é mais sucinto do que fazer uma comparação no valor de retorno. Eu gostaria que o JavaScript tivesse uma in_arrayfunção que retorne um booleano diretamente (semelhante ao PHP), mas isso é apenas uma ilusão ( atualização: agora funciona).includes . Veja acima). Observe que o jQuery inArray, enquanto compartilha a assinatura do método PHP, na verdade imita a indexOffuncionalidade nativa (que é útil em diferentes casos, se o índice for o que você realmente procura).

Nota importante: O uso do atalho til parece estar envolvido em controvérsias, pois alguns acreditam veementemente que o código não é suficientemente claro e deve ser evitado a todo custo (consulte os comentários nesta resposta). Se você compartilha o sentimento deles, deve se ater ao.indexOf(...) >= 0 solução.


Um pouco mais de explicação:

Inteiros em JavaScript são assinados, o que significa que o bit mais à esquerda é reservado como o bit de sinal; uma bandeira para indicar se o número é positivo ou negativo, com um1 negativo.

Aqui estão alguns exemplos de números positivos no formato binário de 32 bits:

1 :    00000000000000000000000000000001
2 :    00000000000000000000000000000010
3 :    00000000000000000000000000000011
15:    00000000000000000000000000001111

Agora, aqui estão os mesmos números, mas negativos:

-1 :   11111111111111111111111111111111
-2 :   11111111111111111111111111111110
-3 :   11111111111111111111111111111101
-15:   11111111111111111111111111110001

Por que combinações tão estranhas para os números negativos? Simples. Um número negativo é simplesmente o inverso do número positivo + 1; adicionar o número negativo ao número positivo deve sempre gerar0 .

Para entender isso, vamos fazer uma aritmética binária simples.

Aqui está como adicionaríamos -1a +1:

   00000000000000000000000000000001      +1
+  11111111111111111111111111111111      -1
-------------------------------------------
=  00000000000000000000000000000000       0

E aqui está como adicionaríamos -15a +15:

   00000000000000000000000000001111      +15
+  11111111111111111111111111110001      -15
--------------------------------------------
=  00000000000000000000000000000000        0

Como obtemos esses resultados? Fazendo acréscimos regulares, da maneira como fomos ensinados na escola: você começa na coluna mais à direita e soma todas as linhas. Se a soma for maior que o maior número de um dígito (que é decimal 9, mas em binário 1), transportamos o restante para a próxima coluna.

Agora, como você notará, ao adicionar um número negativo ao número positivo, a coluna mais à direita, que não é todos os 0s, sempre terá dois 1s, que, quando somados, resultarão em 2. A representação binária de dois seres 10, carregamos a 1para a próxima coluna e colocamos um 0para o resultado na primeira coluna. Todas as outras colunas à esquerda têm apenas uma linha com a 1, portanto, a 1passagem da coluna anterior será adicionada novamente 2, que será transferida ... Esse processo se repete até chegar à coluna mais à esquerda, onde o 1transportador não tem para onde ir, então transborda e se perde, e ficamos com 0s por toda parte.

Este sistema é chamado de Complemento 2 . Você pode ler mais sobre isso aqui:

Representação do complemento 2 para números inteiros assinados .


Agora que o curso intensivo do complemento 2 terminou, você notará que esse -1é o único número cuja representação binária está 1em todo o lado.

Usando o ~operador NOT bit a bit, todos os bits em um determinado número são invertidos. A única maneira de 0voltar a inverter todos os bits é se começarmos com 1todos os lados.

Então, tudo isso foi um longo modo de dizer que ~nsó voltará 0se nfor -1.

Joseph Silber
fonte
59
Embora o uso de operadores bit a bit com certeza seja sexy, é realmente melhor do que !== -1de qualquer maneira concebível? A lógica booleana explícita não é mais apropriada do que implicitamente usar a falsey-ness de zero?
Phil
21
Bem techy, mas eu não gosto. Não está claro à primeira vista o que o código está fazendo, o que o torna insustentável. Eu prefiro a resposta de "Yuriy Galanter".
precisa saber é o seguinte
65
-1 novos programadores ver respostas como este, acho que é um legal e aceitável maneira de codificar, em seguida, em 5 anos eu tenho que manter o seu código e arrancar os cabelos
BlueRaja - Danny Pflughoeft
23
Definitivamente, esse idioma não é comum em linguagens como C #, Java ou Python, que são minhas áreas de especialização. E eu acabei de perguntar a alguns dos especialistas locais em Javascript aqui, e nenhum deles já viu isso antes; portanto, claramente não é tão comum quanto você afirma. Deve sempre ser evitado em favor do muito mais claro e mais comum != -1.
BlueRaja # Danny Pflughoeft
12
-1 devido a manipulação de bits desnecessária em um idioma que realmente não especifica muito sobre representações de bits em primeiro lugar. Além disso, a maior parte desta resposta está explicando os truques de bits. Se você precisar escrever 20 parágrafos para explicar o hack, isso REALMENTE economiza algum tempo?
fofo
242

Você pode usar a instrução switch com fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
Yuriy Galanter
fonte
9
é basicamente o mesmo que o if, o índice do método array é muito melhor
NimChimpsky
6
@kojiro, infelizmente, a resposta certa é essa, mas é impossível chamar a atenção para isso, em vez do incrível truque bitwhise-array.
precisa saber é o seguinte
1
Eu sabia que essa resposta tinha que estar aqui, mas eu tinha que rolar até o final para encontrá-la. É exatamente para isso que a instrução switch foi projetada e transporta para muitos outros idiomas. Eu descobri que muitas pessoas não sabem sobre o método 'fall through' em uma instrução switch.
Jimmy Johnson
3
Esta solução é a mais rápida no Firefox e Safari e a segunda mais rápida (depois da original ||) no Chrome. Veja jsperf.com/if-statements-test-techsin
pabouk
3
Eu acho que seria péssimo escrever um método se você tivesse muitas condições ... Se fosse bom para algumas condições, mas por mais de 10 eu iria para a matriz para manter o código limpo.
yu_ominae 28/08
63

Usando Science: você deve fazer o que o idfah disse e isso para obter a velocidade mais rápida, mantendo o código curto:

ISSO É MAIS RÁPIDO DO QUE O ~Método

var x = test.type;
if (x == 'itema' ||
    x == 'itemb' ||
    x == 'itemc' ||
    x == 'itemd') {
    //do something
}

http://jsperf.com/if-statements-test-techsin insira a descrição da imagem aqui (conjunto superior: Chrome, conjunto inferior: Firefox)

Conclusão:

Se houver possibilidades poucos e você sabe que certas pessoas são mais prováveis de ocorrer do que você obter o máximo desempenho fora if ||, switch fall througheif(obj[keyval]) .

Se houver possibilidades muitas , e qualquer uma delas pode ser a que mais ocorre, em outras palavras, você não sabe o que é mais provável de ocorrer do que obtém o máximo desempenho da pesquisa de objeto if(obj[keyval])e regexse isso se encaixa.

http://jsperf.com/if-statements-test-techsin/12

Eu atualizarei se algo novo surgir.

Muhammad Umer
fonte
2
+1 para uma publicação muito boa! Se bem entendi, o switch casemétodo é o mais rápido?
user1477388
1
no Firefox, sim, em cromo suaif ( ...||...||...)...
Muhammad Umer
8
É mais rápido se você realmente faz muitos loops sobre essa entrada, mas é muito mais lento se você tiver um loop com n muito grande (número de strings "itemX"). Eu cortei este gerador de código que você pode usar para verificar (ou talvez refutar). obj["itemX"]é extremamente rápido se n for grande. Basicamente, o que é rápido depende do contexto. Diverta-se.
Kojiro #
3
Portanto, é o método mais rápido, mas isso importa ?
congusbongus
1
@ Michelle Não sacrifique a elegância do código apenas pela velocidade. Isto é o que muitas pessoas lhe dirão. No final, use apenas o bom senso.
Andre Figueiredo
32

Se você estiver comparando com seqüências de caracteres e houver um padrão, considere usar expressões regulares.

Caso contrário, suspeito que tentar reduzi-lo apenas ofuscará seu código. Considere simplesmente envolver as linhas para torná-lo bonito.

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
idfah
fonte
4
esta resposta é o vencedor em termos de velocidade jsperf.com/if-statements-test-techsin
Muhammad Umer
1
Esta é também a mais fácil de se expandir quando o projeto entra em modo de manutenção (com regras como, (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

Usar um objeto como uma matriz associativa é uma coisa bastante comum, mas como o JavaScript não tem um conjunto nativo, você também pode usar objetos como conjuntos baratos.

kojiro
fonte
Como isso é mais curto que o normal se a declaração de que o FlyingCat está tentando diminuir?
Dcarson
1
A condicional da ifinstrução @dcarson OP ocupa 78 caracteres se você remover todo o espaço em branco. Mina leva 54 se você escrevê-lo como este: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. Fundamentalmente, ele usa quatro caracteres para cada duas minas usadas para cada chave adicional.
Kojiro #
1
mas você pode fazer: if (possibilidades [test.type]) e salvar 2 caracteres inteiros! :)
DC5
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

ou se os itens não forem tão uniformes, então:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Matt
fonte
9
"Algumas pessoas, quando confrontadas com um problema, pensam 'eu sei, vou usar expressões regulares'. Agora eles tem dois problemas." - Jamie Zawinski, 1997
Moshe Katz
5
@MosheKatz Embora eu possa entender que as pessoas gostam de se distrair com essa citação - e as pessoas certamente usam expressões regulares para coisas totalmente inadequadas, mas essa não é uma delas. No caso fornecido pelo OP, isso não apenas corresponde aos critérios, mas muito bem. Expressões regulares não são inerentemente más, e combinações de strings com parâmetros bem definidos é para isso que ela foi criada.
precisa saber é o seguinte
3
@ Thor84no Normalmente, eu suporia que o interlocutor não está realmente tentando corresponder a um exemplo tão artificial quanto o primeiro caso, e que as correspondências do mundo real não são tão simples, nesse caso, não acho que um RegEx esteja indo ser o caminho certo para fazê-lo. Em outras palavras, se o seu RegEx é apenas uma lista de opções separadas por caracteres de barra, não é mais legível que nenhuma das outras sugestões e, possivelmente, significativamente menos eficiente.
Moshe Katz
10

Respostas excelentes, mas você pode tornar o código muito mais legível envolvendo uma delas em uma função.

É uma declaração complexa, se você (ou outra pessoa) ler o código dentro de alguns anos, estará pesquisando para encontrar a seção para entender o que está acontecendo. Uma declaração com esse nível de lógica de negócios fará com que você tropece por alguns segundos enquanto trabalha no que está testando. Onde um código como este, permitirá que você continue a digitalização.

if(CheckIfBusinessRuleIsTrue())
{
    //Do Something
}

function CheckIfBusinessRuleIsTrue() 
{
    return (the best solution from previous posts here);
}

Nomeie sua função explicitamente para que fique óbvio imediatamente o que você está testando e seu código será muito mais fácil de digitalizar e entender.

Fran Hoey
fonte
1
Melhor resposta que eu já vi aqui. Realmente, vejo que as pessoas não se importam com os princípios do bom design. Só quero consertar algo rapidamente e esqueça de melhorar o código para que o sistema possa ser mantido no futuro!
Maykonn 27/08/13
Que tal apenas comentar como // CheckIfBusinessRuleIsTrue?
daniel1426
4

Você pode colocar todas as respostas em um conjunto Javascript e depois chamar.contains() o conjunto.

Você ainda precisa declarar todo o conteúdo, mas a chamada em linha será mais curta.

Algo como:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
Guido Anselmi
fonte
4
Parece uma maneira incrivelmente inútil de realizar o que o OP está tentando fazer. Portanto, embora você possa incluir uma biblioteca extra de terceiros, instanciar um objeto e chamar um método, provavelmente não deveria.
precisa saber é o seguinte
@ Capitão Cold: Bem, o OP pediu concisão, não pegada de memória. Talvez o aparelho possa ser reutilizado para outras operações?
Guido Anselmi
1
Claro, mas mesmo assim: Você com toda a honestidade que nunca fazer isso sozinho? Se eu já visse isso na natureza, consideraria uma grande WTF.
KaptajnKold
1
Sim, você está certo (eu dei os + 1s), mas assume que essa verificação não está sendo feita em nenhum outro lugar. Se estiver sendo realizado em vários outros locais e / ou o teste for alterado, o uso do Conjunto poderá fazer sentido. Deixo ao OP escolher a melhor solução. Tudo o que disse se esse era um uso solitário, eu concordaria que o uso do Conjunto mereceria o chapéu de vergonha do As Clown.
Guido Anselmi
2
Na verdade, acho que a resposta escolhida é absolutamente horrível e pior do que usar o conjunto, pois é absolutamente ilegível.
Guido Anselmi
2

Uma das minhas maneiras favoritas de fazer isso é com uma biblioteca como underscore.js ...

var isItem = _.some(['itema','itemb','itemc','itemd'], function(item) {
    return test.type === item;
});

if(isItem) {
    // One of them was true
}

http://underscorejs.org/#some

jcreamer898
fonte
1
containsé sem dúvida uma solução melhor do quesome
Dennis
1
Não é necessário usar uma biblioteca para isso: someé uma função no protótipo Array no EC5.
precisa saber é o seguinte
2
É verdade, mas nem todos têm suporte para EC5 disponível. Além disso, eu realmente gosto de sublinhado. :)
jcreamer898
Se você estiver usando uma biblioteca como sublinhado, esta é provavelmente a maneira mais fácil. Caso contrário, faz pouco sentido carregar uma biblioteca inteira apenas para uma função.
Moshe Katz
2

outra maneira ou outra maneira incrível que eu encontrei é essa ...

if ('a' in oc(['a','b','c'])) { //dosomething }

function oc(a)
{
  var o = {};
  for(var i=0;i<a.length;i++)  o[a[i]]='';
  return o;
}

é claro que, como você pode ver, isso leva as coisas um passo adiante e facilita o acompanhamento da lógica.

http://snook.ca/archives/javascript/testing_for_a_v

usando operadores como ~ && || ((), ()) ~~ só funciona se o seu código quebrar mais tarde. Você não saberá por onde começar. Portanto, a legibilidade é GRANDE.

se necessário, você pode torná-lo mais curto.

('a' in oc(['a','b','c'])) && statement;
('a' in oc(['a','b','c'])) && (statements,statements);
('a' in oc(['a','b','c']))?statement:elseStatement;
('a' in oc(['a','b','c']))?(statements,statements):(elseStatements,elseStatements);

e se você quiser fazer inverso

('a' in oc(['a','b','c'])) || statement;
Muhammad Umer
fonte
2

Basta usar uma switchdeclaração em vez de uma ifdeclaração:

switch (test.type) {

  case "itema":case "itemb":case "itemc":case "itemd":
    // do your process
  case "other cases":...:
    // do other processes
  default:
    // do processes when test.type does not meet your predictions.
}

Switch também funciona mais rápido do que comparar muitos condicionais dentro de um if

unmultimedio
fonte
2

Para listas muito longas de strings, essa idéia salvaria alguns caracteres (sem dizer que eu recomendaria na vida real, mas deve funcionar).

Escolha um caractere que você sabe que não ocorrerá em seu test.type, use-o como um delimitador, cole todos em uma sequência longa e pesquise:

if ("/itema/itemb/itemc/itemd/".indexOf("/"+test.type+"/")>=0) {
  // doSomething
}

Se suas seqüências de caracteres estiverem mais restritas, você pode até omitir os delimitadores ...

if ("itemaitembitemcitemd".indexOf(test.type)>=0) {
  // doSomething
}

... mas você deve ter cuidado com falsos positivos nesse caso (por exemplo, "embite" corresponderia nessa versão)

CupawnTae
fonte
2

Para facilitar a leitura, crie uma função para o teste (sim, uma função de uma linha):

function isTypeDefined(test) {
    return test.type == 'itema' ||
           test.type == 'itemb' ||
           test.type == 'itemc' ||
           test.type == 'itemd';
}

então chame-o:


    if (isTypeDefined(test)) {

}
...
zaph
fonte
1

Eu acho que existem 2 objetivos ao escrever esse tipo de condição.

  1. brevidade
  2. legibilidade

Como tal, algumas vezes o nº 1 pode ser o mais rápido, mas usarei o nº 2 para facilitar a manutenção posteriormente. Dependendo do cenário, geralmente optarei por uma variação da resposta de Walter.

Para começar, tenho uma função disponível globalmente como parte da minha biblioteca existente.

function isDefined(obj){
  return (typeof(obj) != 'undefined');
}

e quando eu realmente quiser executar uma condição if semelhante à sua, criaria um objeto com uma lista dos valores válidos:

var validOptions = {
  "itema":1,
  "itemb":1,
  "itemc":1,
  "itemd":1
};
if(isDefined(validOptions[test.type])){
  //do something...
}

Não é tão rápido quanto uma declaração switch / case e é um pouco mais detalhado do que alguns dos outros exemplos, mas geralmente recebo a reutilização do objeto em outras partes do código, o que pode ser bastante útil.

Pegando carona em uma das amostras jsperf feitas acima, adicionei este teste e uma variação para comparar velocidades. http://jsperf.com/if-statements-test-techsin/6 A coisa mais interessante que notei é que certos combos de teste no Firefox são muito mais rápidos que o Chrome.

scunliffe
fonte
1

Isso pode ser resolvido com um simples loop for:

test = {};
test.type = 'itema';

for(var i=['itema','itemb','itemc']; i[0]==test.type && [
    (function() {
        // do something
        console.log('matched!');
    })()
]; i.shift());

Usamos a primeira seção do loop for para inicializar os argumentos que você deseja corresponder, a segunda seção para interromper a execução do loop for e a terceira seção para fazer com que o loop saia eventualmente.


fonte