Come abbreviare le mie dichiarazioni condizionali


154

Ho una dichiarazione condizionale molto lunga come la seguente:

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

Mi chiedevo se avrei potuto riformattare questa espressione / affermazione in una forma più concisa.

Qualche idea su come raggiungere questo obiettivo?


23
Puoi metterli in un array e usarli in?
Jeremy,


ora solo se qualcuno potesse verificare qual è il più veloce
Muhammad Umer,

3
questo forse è uno shock per tutti, ma ciò che ha OP è un chiaro vincitore della velocità !!!!!!! Forse perché il browser ottimizza molto per questo. Risultati: (1) se con ||. (2) switchdichiarazioni. (3) regex. (4) ~. jsperf.com/if-statements-test-techsin
Muhammad Umer

3
Potresti anche affrontarlo nel modo sbagliato. In questo caso, quei 4 tipi hanno qualcosa in comune. Che cos'è? Se prendiamo questo in un caso più estremo, se avessimo bisogno di aggiungere altri 10 tipi per soddisfare questa condizione. O 100? Se ce ne fossero altri, probabilmente non prenderesti in considerazione l'utilizzo di questa soluzione, o uno qualsiasi degli altri suggerito. Stai vedendo una grande dichiarazione if come questa e pensi che sia un odore di codice, che è un buon segno. Il modo migliore per rendere tutto più conciso sarebbe se tu potessi scrivere if (test.your_common_condition). È più facile da capire in questo contesto e più estensibile.
gmacdougall,

Risposte:


241

Inserisci i tuoi valori in un array e controlla se il tuo articolo è nell'array:

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

Se un browser supportato non ha il Array#includesmetodo, è possibile utilizzare questo polyfill .


Breve spiegazione della ~scorciatoia tilde:

Aggiornamento: dato che ora abbiamo il includesmetodo, non ha più senso utilizzare l' ~hacking. Tieni questo qui solo per le persone che sono interessate a sapere come funziona e / o che lo hanno riscontrato nel codice di altri.

Invece di verificare se il risultato di indexOfè >= 0, esiste una piccola scorciatoia:

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

Ecco il violino: http://jsfiddle.net/HYJvK/

Come funziona? Se viene trovato un elemento nell'array, indexOfrestituisce il suo indice. Se l'articolo non è stato trovato, tornerà -1. Senza entrare troppo nei dettagli, ~è un operatore NOT bit per bit , che tornerà 0solo per -1.

Mi piace usare la ~scorciatoia, poiché è più concisa che fare un confronto sul valore restituito. Vorrei che JavaScript avesse una in_arrayfunzione che restituisse direttamente un booleano (simile a PHP), ma è solo un pio desiderio ( Aggiornamento: adesso lo fa. Si chiama includes. Vedi sopra). Nota che jQuery inArray, pur condividendo la firma del metodo PHP, imita effettivamente la indexOffunzionalità nativa (che è utile in diversi casi, se l'indice è ciò che stai veramente cercando).

Nota importante: l' uso della scorciatoia tilde sembra essere oggetto di controversie, poiché alcuni credono con veemenza che il codice non sia abbastanza chiaro e dovrebbe essere evitato a tutti i costi (vedere i commenti su questa risposta). Se condividi il loro sentimento, dovresti attenersi alla .indexOf(...) >= 0soluzione.


Una spiegazione un po 'più lunga:

I numeri interi in JavaScript sono firmati, il che significa che il bit più a sinistra è riservato come bit di segno; una bandiera per indicare se il numero è positivo o negativo, con un 1essere negativo.

Ecco alcuni numeri positivi di esempio in formato binario a 32 bit:

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

Ora ecco gli stessi numeri, ma negativi:

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

Perché combinazioni così strane per i numeri negativi? Semplice. Un numero negativo è semplicemente l'inverso del numero positivo + 1; l'aggiunta del numero negativo al numero positivo dovrebbe sempre produrre 0.

Per capirlo, facciamo un po 'di semplice aritmetica binaria.

Ecco come aggiungeremmo -1a +1:

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

Ed ecco come aggiungeremmo -15a +15:

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

Come possiamo ottenere quei risultati? Facendo regolarmente aggiunte, il modo in cui ci è stato insegnato a scuola: inizi dalla colonna più a destra e sommi tutte le righe. Se la somma è maggiore del massimo numero a una cifra (che è in decimale 9, ma in binario è 1), il resto viene trasferito alla colonna successiva.

Ora, come noterai, quando aggiungi un numero negativo al suo numero positivo, la colonna più a destra che non è tutte le 0s avrà sempre due 1s, che una volta sommate comporteranno 2. La rappresentazione binaria di due essere 10, portiamo la 1colonna successiva e inseriamo un 0risultato nella prima colonna. Tutte le altre colonne a sinistra hanno solo una riga con a 1, quindi il 1riporto dalla colonna precedente si aggiungerà di nuovo a 2, che poi riporterà ... Questo processo si ripete fino a raggiungere la colonna più a sinistra, dove l' 1essere trasportato non ha nessun posto dove andare, quindi trabocca e si perde, e ci rimane 0tutto s.

Questo sistema si chiama Complemento 2 . Puoi leggere di più su questo qui:

Rappresentazione del complemento di 2 per numeri interi firmati .


Ora che il corso intensivo nel complemento di 2 è terminato, noterai che -1è l'unico numero la cui rappresentazione binaria è in 1tutto.

Utilizzando l' ~operatore NOT bit a bit, tutti i bit di un dato numero vengono invertiti. L'unico modo per tornare 0indietro dall'inversione di tutti i bit è se iniziamo con 1tutto.

Quindi, tutto questo era un modo prolisso di dire che ~ntornerà solo 0se lo nè -1.


59
Mentre usare operatori bit per bit è sicuramente sexy, è davvero meglio che !== -1in qualsiasi modo immaginabile? La logica booleana esplicita non è più appropriata dell'uso implicito della falsità di zero?
Phil

21
Piacevolmente tecnico, ma non mi piace. A prima vista non è chiaro cosa stia facendo il codice, il che lo rende non mantenibile. Preferisco di gran lunga la risposta di "Yuriy Galanter".
Jon Rea,

65
-1 nuovi programmatori vedono risposte come questa, pensano che sia un modo simpatico e accettabile di scrivere codice, quindi tra 5 anni devo mantenere il loro codice e strapparmi i capelli
BlueRaja - Danny Pflughoeft

23
Questo linguaggio non è sicuramente comune in linguaggi come C #, Java o Python, che sono le mie aree di competenza. E ho appena chiesto ad alcuni degli esperti Javascript locali qui, e nessuno di loro ha mai visto farlo prima; quindi chiaramente non è così comune come si afferma. Dovrebbe essere sempre evitato a favore di ciò che è molto più chiaro e comune != -1.
BlueRaja - Danny Pflughoeft,

12
-1 a causa del bitfiddling non necessario in una lingua che in primo luogo non specifica molto sulle rappresentazioni dei bit. Inoltre, la maggior parte di questa risposta sta spiegando il bitfiddling. Se devi scrivere 20 paragrafi per spiegare l'hack, fa davvero risparmiare tempo?
soffice

242

È possibile utilizzare l'istruzione switch con fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}

9
è sostanzialmente lo stesso del if, l'indice del metodo array è molto migliore
NimChimpsky

6
@kojiro è tristemente, la risposta giusta è questa, ma è impossibile attirare l'attenzione su questo invece del fantastico trucco bitwhise-array.
Manu343726,

1
Sapevo che questa risposta doveva essere qui, ma ho dovuto scorrere fino in fondo per trovarla. È esattamente ciò per cui l'istruzione switch è stata progettata e trasporta in molte altre lingue. Ho scoperto che molte persone non conoscono il metodo "fall through" in un'istruzione switch.
Jimmy Johnson,

3
Questa soluzione è la più veloce su Firefox e Safari e la seconda più veloce (dopo l'originale ||) su Chrome. Vedi jsperf.com/if-statements-test-techsin
pabouk il

3
Penso che sarebbe terribile scrivere in un metodo se avessi molte condizioni ... Andrebbe bene per alcune condizioni, ma per più di 10 andrei per l'array per mantenere pulito il codice.
yu_ominae,

63

Usando la scienza: dovresti fare ciò che ha detto idfah e questo per la massima velocità, mantenendo il codice breve:

QUESTO È PIÙ VELOCE DEL ~Metodo

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

http://jsperf.com/if-statements-test-techsin inserisci qui la descrizione dell'immagine (Set superiore: Chrome, set inferiore: Firefox)

Conclusione :

Se le possibilità sono poche e si sa che alcuni sono più probabilità di verificarsi di quanto si ottiene il massimo delle prestazioni fuori if ||, switch fall throughe if(obj[keyval]).

Se le possibilità sono molte , e chiunque di loro potrebbe essere il più presente, in altre parole, non puoi sapere quale è la probabilità che si verifichi più di quanto ottieni la maggior parte delle prestazioni dalla ricerca degli oggetti if(obj[keyval])e regexse si adatta.

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

aggiornerò se arriva qualcosa di nuovo.


2
+1 per un post davvero buono! Se capisco correttamente, switch caseè il metodo più veloce?
user1477388

1
in Firefox sì, in Chrome èif ( ...||...||...)...
Muhammad Umer il

8
È più veloce se esegui davvero molti loop su questo input, ma è molto più lento se hai un loop con n molto grande (numero di stringhe "itemX"). Ho hackerato questo generatore di codice che puoi usare per verificare (o forse confutare). obj["itemX"]è estremamente veloce se n è grande. Fondamentalmente, ciò che è veloce dipende dal contesto. Divertiti.
Kojiro,

3
Quindi è il metodo più veloce, ma importa ?
congusbongus,

1
@Mich Non sacrificare l'eleganza del codice solo per la velocità. Questo è ciò che molte persone ti diranno. Alla fine, basta usare il buon senso.
Andre Figueiredo,

32

Se stai confrontando con stringhe e c'è un modello, considera l'utilizzo di espressioni regolari.

Altrimenti, sospetto che il tentativo di accorciarlo offuschi il tuo codice. Considera semplicemente di avvolgere le linee per renderlo carino.

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

4
questa risposta è il vincitore in termini di velocità jsperf.com/if-statements-test-techsin
Muhammad Umer

1
Questo è anche il più facile da espandere quando il progetto entra in modalità di manutenzione (con regole come, (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata

16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

L'uso di un oggetto come un array associativo è una cosa abbastanza comune, ma poiché JavaScript non ha un set nativo, puoi usare gli oggetti anche come set economici.


In che modo è più breve della normale dichiarazione if che FlyingCat sta cercando di abbreviare?
dcarson,

1
ifLa condizione dell'istruzione @dcarson OP richiede 78 caratteri se si rimuove tutto lo spazio bianco. La mia prende 54 se si scrive in questo modo: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. Fondamentalmente, usa quattro caratteri per ogni due mine per ogni chiave aggiuntiva.
Kojiro,

1
ma puoi fare: if (Possibilità [test.type]) e salvare 2 caratteri interi! :)
dc5

15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

o se gli articoli non sono così uniformi, allora:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }

9
"Alcune persone, di fronte a un problema, pensano" Lo so, userò espressioni regolari ". Ora hanno due problemi ". - Jamie Zawinski, 1997
Moshe Katz,

5
@MosheKatz Mentre riesco a capire che alla gente piace bandire quella citazione - e le persone certamente usano espressioni regolari per cose del tutto inadatte, ma questa non è una di queste. Nel caso previsto dal PO, questo non corrisponde solo ai criteri, ma lo fa molto bene. Le espressioni regolari non sono intrinsecamente malvagie e abbinare stringhe con parametri ben definiti è ciò per cui è stato creato.
Thor84no,

3
@ Thor84no Di solito, suppongo che l'interrogatore non stia effettivamente cercando di confrontarsi con un esempio così forzato come il primo caso, e che le partite del mondo reale non sono così semplici, nel qual caso non penso che un RegEx stia andando per essere il modo giusto per farlo. Per dirla in altro modo, se RegEx è solo un elenco di opzioni separato da caratteri di pipa, non è più leggibile rispetto a nessuno degli altri suggerimenti e probabilmente significativamente meno efficiente.
Moshe Katz,

10

Risposte eccellenti, ma potresti rendere il codice molto più leggibile racchiudendone uno in una funzione.

Questa è un'affermazione complessa se, quando tu (o qualcun altro) leggerai il codice tra un anno, eseguirai la scansione per trovare la sezione per capire cosa sta succedendo. Una dichiarazione con questo livello di logica aziendale ti farà inciampare per alcuni secondi mentre elaborerai ciò che stai testando. Dove come codice come questo, ti permetterà di continuare la scansione.

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

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

Assegna un nome esplicito alla tua funzione in modo che sia immediatamente ovvio cosa stai testando e il tuo codice sarà molto più facile da scansionare e capire.


1
La migliore risposta che ho visto qui. Davvero, vedo che alla gente non interessano i principi di un buon design. Voglio solo riparare qualcosa rapidamente e dimenticare di migliorare il codice in modo che il sistema sia mantenibile in futuro!
Maykonn,

Che ne dici di commentare come // CheckIfBusinessRuleIsTrue?
daniel1426,

4

È possibile inserire tutte le risposte in un set Javascript e quindi chiamare .contains()il set.

Devi ancora dichiarare tutto il contenuto, ma la chiamata in linea sarà più breve.

Qualcosa di simile a:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}

4
Sembra un modo incredibilmente dispendioso per realizzare ciò che l'OP sta cercando di fare. Così, mentre si potrebbe includere una libreria in più 3rd-party, un'istanza di un oggetto, e la chiamata di un metodo su di esso, probabilmente non dovrebbe.
KaptajnKold,

@Captain Cold: Beh, l'OP ha chiesto concisione, non impronta di memoria. Forse il set potrebbe essere riutilizzato per altre operazioni?
Guido Anselmi,

1
Certo, ma anche così: lo faresti in tutta onestà da solo? Se mai lo vedessi in natura, lo considererei un WTF importante.
KaptajnKold,

1
Sì, hai ragione (ti ho dato i +1) ma si presume che questo controllo non sia stato fatto da nessun'altra parte. Se lo si sta facendo in molti altri posti e / o il test cambia, l'uso del Set potrebbe avere senso. Lascio all'OP la scelta della soluzione migliore. Detto questo, se questo fosse un uso solitario, sarei d'accordo che l'uso del Set meriterebbe il cappello della vergogna di As Clown.
Guido Anselmi,

2
In realtà penso che la risposta scelta sia assolutamente terribile e peggio che usare il Set in quanto è assolutamente illeggibile.
Guido Anselmi,

2

Uno dei miei modi preferiti per raggiungere questo obiettivo è con una libreria come 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


1
containsè probabilmente una soluzione migliore disome
Dennis il

1
Non è necessario utilizzare una libreria per questo: someè una funzione sul prototipo di array in EC5.
KaptajnKold,

2
Vero, ma non tutti hanno il supporto EC5 disponibile. Inoltre, mi piace davvero solo il trattino basso. :)
jcreamer898,

Se stai già utilizzando una libreria come il trattino basso, questo è probabilmente il modo più semplice. Altrimenti, ha poco senso caricare un'intera libreria solo per l'unica funzione.
Moshe Katz,

2

in un altro modo o in un altro fantastico modo che ho trovato è questo ...

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;
}

ovviamente, come puoi vedere, questo fa un ulteriore passo avanti e semplifica la logica.

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

utilizzando operatori come ~ && || ((), ()) ~~ va bene solo se il codice si rompe in seguito. Non saprai da dove iniziare. Quindi la leggibilità è GRANDE.

se devi, potresti accorciarlo.

('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 vuoi fare inverso

('a' in oc(['a','b','c'])) || statement;

2

Basta usare switchun'istruzione anziché ifun'istruzione:

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 funziona anche più velocemente rispetto al confronto di molti condizionali all'interno di un if


2

Per elenchi di stringhe molto lunghi, questa idea salverebbe alcuni caratteri (senza dire che lo consiglierei nella vita reale, ma dovrebbe funzionare).

Scegli un carattere che sai non si verificherà nel tuo test.type, usalo come delimitatore, inseriscili tutti in una lunga stringa e cerca che:

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

Se le tue stringhe sono ulteriormente vincolate, potresti persino omettere i delimitatori ...

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

... ma dovresti fare attenzione ai falsi positivi in ​​quel caso (ad es. "embite" corrisponderebbe a quella versione)


2

Per la leggibilità, creare una funzione per il test (sì, una funzione a una riga):

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

quindi chiamalo:


    if (isTypeDefined(test)) {

}
...

1

Penso che ci siano 2 obiettivi quando si scrive questo tipo di if condition.

  1. concisione
  2. leggibilità

Pertanto, a volte il n. 1 potrebbe essere il più veloce, ma in seguito prenderò il n. 2 per una facile manutenzione. A seconda dello scenario, sceglierò spesso una variante della risposta di Walter.

Per iniziare, ho una funzione disponibile a livello globale come parte della mia libreria esistente.

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

e poi quando in realtà voglio eseguire una condizione if simile alla tua creerei un oggetto con un elenco di valori validi:

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

Non è veloce come un'istruzione switch / case e un po 'più dettagliato di alcuni degli altri esempi, ma spesso riesco a riutilizzare l'oggetto altrove nel codice che può essere abbastanza utile.

Piggybacking su uno dei campioni jsperf fatti sopra ho aggiunto questo test e una variazione per confrontare le velocità. http://jsperf.com/if-statements-test-techsin/6 La cosa più interessante che ho notato è che alcune combo di test in Firefox sono molto più veloci di Chrome.


1

Questo può essere risolto con un semplice ciclo per:

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

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

Usiamo la prima sezione del ciclo for per inizializzare gli argomenti che si desidera abbinare, la seconda sezione per interrompere l'esecuzione del ciclo for e la terza sezione per causare la fine del ciclo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.