Comment raccourcir mes déclarations conditionnelles

154

J'ai une très longue déclaration conditionnelle comme celle-ci:

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

Je me demandais si je pouvais refactoriser cette expression / déclaration sous une forme plus concise.

Une idée sur la façon d'y parvenir?

FlyingCat
la source
23
Vous pouvez les mettre dans un tableau et les utiliser in?
jeremy
maintenant seulement si quelqu'un pouvait vérifier lequel est le plus rapide
Muhammad Umer
3
c'est peut-être un choc pour tout le monde mais ce qu'OP a est un gagnant clair en vitesse !!!!!!! Peut-être que le navigateur optimise beaucoup pour cela. Résultats: (1) si avec ||. (2) switchdéclarations. (3) regex. (4) ~. jsperf.com/if-statements-test-techsin
Muhammad Umer
3
Vous pouvez également aborder cela de la mauvaise manière. Dans ce cas, ces 4 types ont quelque chose en commun. Qu'Est-ce que c'est? Si nous prenons cela à un cas plus extrême, que se passerait-il si nous devions ajouter 10 types supplémentaires pour répondre à cette condition. Ou 100? S'il y en avait plus, vous n'envisageriez probablement pas d'utiliser cette solution ou l'une des autres suggérées. Vous voyez une grande déclaration if comme celle-ci et vous pensez que c'est une odeur de code, ce qui est bon signe. Votre meilleure façon de rendre cela plus concis serait d'écrire if (test.your_common_condition). C'est plus facile à comprendre dans ce contexte, et plus extensible.
gmacdougall

Réponses:

241

Mettez vos valeurs dans un tableau et vérifiez si votre élément est dans le tableau:

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

Si un navigateur que vous prenez en charge ne dispose pas de la Array#includesméthode, vous pouvez utiliser ce polyfill .


Brève explication du ~raccourci tilde:

Mise à jour: Puisque nous avons maintenant la includesméthode, il ne ~sert plus à rien d'utiliser le hack. Il suffit de garder ceci ici pour les personnes qui souhaitent savoir comment cela fonctionne et / ou qui l'ont rencontré dans le code d'un autre.

Au lieu de vérifier si le résultat de indexOfest >= 0, il y a un joli petit raccourci:

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

Voici le violon: http://jsfiddle.net/HYJvK/

Comment cela marche-t-il? Si un élément est trouvé dans le tableau, indexOfrenvoie son index. Si l'article n'a pas été trouvé, il reviendra -1. Sans entrer dans les détails, le ~est un opérateur NOT au niveau du bit , qui ne retournera 0que pour -1.

J'aime utiliser le ~raccourci, car il est plus succinct que de faire une comparaison sur la valeur de retour. Je souhaite que JavaScript ait une in_arrayfonction qui renvoie directement un booléen (similaire à PHP), mais c'est juste un vœu pieux ( Mise à jour: c'est maintenant le cas. Il est appelé includes. Voir ci-dessus). Notez que jQuery inArray, tout en partageant la signature de méthode PHP, imite en fait la indexOffonctionnalité native (ce qui est utile dans différents cas, si l'index est ce que vous recherchez vraiment).

Remarque importante: l' utilisation du raccourci tilde semble être controversée, car certains pensent avec véhémence que le code n'est pas assez clair et doit être évité à tout prix (voir les commentaires sur cette réponse). Si vous partagez leur sentiment, vous devez vous en tenir à la .indexOf(...) >= 0solution.


Une explication un peu plus longue:

Les entiers en JavaScript sont signés, ce qui signifie que le bit le plus à gauche est réservé comme bit de signe; un drapeau pour indiquer si le nombre est positif ou négatif, avec un 1négatif.

Voici quelques exemples de nombres positifs au format binaire 32 bits:

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

Voici maintenant ces mêmes chiffres, mais négatifs:

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

Pourquoi des combinaisons aussi étranges pour les nombres négatifs? Facile. Un nombre négatif est simplement l'inverse du nombre positif + 1; l'ajout du nombre négatif au nombre positif doit toujours produire 0.

Pour comprendre cela, faisons une simple arithmétique binaire.

Voici comment nous ajouterions -1à +1:

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

Et voici comment nous ajouterions -15à +15:

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

Comment obtenons-nous ces résultats? En faisant des ajouts réguliers, comme on nous a enseigné à l'école: vous commencez à la colonne la plus à droite et vous additionnez toutes les lignes. Si la somme est supérieure au plus grand nombre à un chiffre (qui en décimal est 9, mais en binaire est 1), nous reportons le reste à la colonne suivante.

Maintenant, comme vous le remarquerez, lorsque vous ajoutez un nombre négatif à son nombre positif, la colonne la plus à droite qui n'est pas tout 0s aura toujours deux 1s, ce qui, une fois additionné, donnera 2. La représentation binaire de deux étant 10, nous portons le 1à la colonne suivante, et mettons un 0pour le résultat dans la première colonne. Toutes les autres colonnes à gauche n'ont qu'une seule ligne avec un 1, donc le report 1de la colonne précédente s'additionnera à nouveau 2, qui sera ensuite reporté ... Ce processus se répète jusqu'à ce que nous arrivions à la colonne la plus à gauche, où le 1à reporter n'a nulle part où aller, donc il déborde et se perd, et nous nous retrouvons avec des 0s partout.

Ce système est appelé Complément 2 . Vous pouvez en savoir plus ici:

Représentation complémentaire de 2 pour les entiers signés .


Maintenant que le cours intensif du complément de 2 est terminé, vous remarquerez que -1c'est le seul nombre dont la représentation binaire est 1de partout.

En utilisant l' ~opérateur NOT au niveau du bit, tous les bits d'un nombre donné sont inversés. La seule façon de revenir en 0arrière de l'inversion de tous les bits est de commencer avec des 1«all across».

Donc, tout cela était une longue façon de dire qui ~nne reviendra que 0si nc'est le cas -1.

Joseph Silber
la source
59
Bien que l'utilisation d'opérateurs au niveau du bit soit vraiment sexy, est-ce vraiment mieux que !== -1de toute manière imaginable? La logique booléenne explicite n'est-elle pas plus appropriée que d'utiliser implicitement la fausseté de zéro?
Phil
21
Bien technique, mais je n'aime pas ça. On ne sait pas à première vue ce que fait le code, ce qui le rend impossible à maintenir. Je préfère de loin la réponse de "Yuriy Galanter".
Jon Rea
65
-1 nouveaux programmeurs voient des réponses comme celle-ci, pensent que c'est une façon cool et acceptable de coder, puis dans 5 ans je dois maintenir leur code et me déchirer les cheveux
BlueRaja - Danny Pflughoeft
23
Cet idiome n'est certainement pas courant dans des langages comme C #, Java ou Python, qui sont mes domaines d'expertise. Et je viens de demander à quelques-uns des experts Javascript locaux ici, et aucun d'entre eux n'a jamais vu cela auparavant; ce n'est donc clairement pas aussi courant que vous le prétendez. Il faut toujours l'éviter au profit du plus clair et du plus courant != -1.
BlueRaja - Danny Pflughoeft
12
-1 en raison d'un bitfiddling inutile dans un langage qui ne spécifie pas vraiment beaucoup sur les représentations de bits en premier lieu. De plus, l'essentiel de cette réponse explique le bitfiddling. Si vous devez écrire 20 paragraphes pour expliquer le hack, cela vous fait-il vraiment gagner du temps?
moelleux
242

Vous pouvez utiliser l'instruction switch avec fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
Yuriy Galanter
la source
9
c'est fondamentalement le même que le if, la méthode d'index de tableau est bien meilleure
NimChimpsky
6
@kojiro, malheureusement, la bonne réponse est la suivante, mais il est impossible d'attirer l'attention sur cela au lieu de l'impressionnante astuce bitwhise-array.
Manu343726
1
Je savais que cette réponse devait être ici, mais je devais faire défiler vers le bas pour la trouver. C'est exactement ce pour quoi l'instruction switch a été conçue et s'applique à de nombreux autres langages. J'ai trouvé que beaucoup de gens ne connaissent pas la méthode «fall through» dans une instruction switch.
Jimmy Johnson
3
Cette solution est la plus rapide sur Firefox et Safari et la deuxième plus rapide (après l'original ||) sur Chrome. Voir jsperf.com/if-statements-test-techsin
pabouk
3
Je pense que ce serait horrible d'écrire dans une méthode si vous aviez de nombreuses conditions ... C'est bien pour quelques conditions, mais pour plus de 10, j'irais pour le tableau pour garder le code propre.
yu_ominae
63

Utilisation de la science: vous devez faire ce que l'idfah a dit et ceci pour une vitesse plus rapide tout en gardant le code court:

CECI EST PLUS RAPIDE QUE LA ~Méthode

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

http://jsperf.com/if-statements-test-techsin entrez la description de l'image ici (Ensemble supérieur: Chrome, ensemble inférieur: Firefox)

Conclusion :

Si les possibilités sont peu et vous savez que certains d'entre eux sont plus susceptibles de se produire que vous obtenez des performances maximales if ||, switch fall throughet if(obj[keyval]).

Si les possibilités sont nombreuses et que n'importe laquelle d'entre elles pourrait être la plus présente, en d'autres termes, vous ne pouvez pas savoir laquelle est la plus susceptible de se produire que vous obtenez le plus de performances de la recherche d'objets if(obj[keyval])et regexsi cela vous convient.

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

Je mettrai à jour si quelque chose de nouveau arrive.

Muhammad Umer
la source
2
+1 pour un très bon post! Si je comprends bien, quelle switch caseest la méthode la plus rapide?
user1477388
1
dans firefox oui, dans chrome c'estif ( ...||...||...)...
Muhammad Umer
8
C'est plus rapide si vous faites vraiment beaucoup de boucles sur cette entrée, mais c'est beaucoup plus lent si vous avez une boucle avec un très grand n (nombre de chaînes "itemX"). J'ai piraté ce générateur de code que vous pouvez utiliser pour vérifier (ou peut-être réfuter). obj["itemX"]est extrêmement rapide si n est grand. Fondamentalement, ce qui est rapide dépend du contexte. S'amuser.
kojiro
3
C'est donc la méthode la plus rapide, mais est-ce important ?
congusbongus
1
@Mich Ne sacrifiez pas l'élégance du code uniquement pour la vitesse. C'est ce que beaucoup de gens vous diront. En fin de compte, utilisez simplement votre bon sens.
Andre Figueiredo
32

Si vous comparez à des chaînes et qu'il existe un modèle, envisagez d'utiliser des expressions régulières.

Sinon, je soupçonne que tenter de le raccourcir ne fera qu'obscurcir votre code. Pensez simplement à envelopper les lignes pour le rendre joli.

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
Idfah
la source
4
cette réponse est la gagnante en termes de vitesse jsperf.com/if-statements-test-techsin
Muhammad Umer
1
C'est également le plus facile à développer lorsque le projet passe en mode maintenance (avec des règles telles que, (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

L'utilisation d'un objet comme tableau associatif est une chose assez courante, mais comme JavaScript n'a pas d'ensemble natif, vous pouvez également utiliser des objets comme ensembles bon marché.

Kojiro
la source
En quoi est-ce plus court que la déclaration normale si FlyingCat tente de raccourcir?
dcarson
1
ifLe conditionnel de l' instruction @dcarson OP prend 78 caractères si vous supprimez tous les espaces. 54 prend le mien si vous écrivez comme ceci: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. Fondamentalement, il utilise quatre caractères pour chaque utilisation de deux mines pour chaque clé supplémentaire.
kojiro
1
mais vous pouvez faire: if (possibilités [test.type]) et enregistrer un ensemble de 2 caractères! :)
dc5
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

ou si les articles ne sont pas aussi uniformes, alors:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Mat
la source
9
"Certaines personnes, confrontées à un problème, pensent" Je sais, j'utiliserai des expressions régulières ". Maintenant, ils ont deux problèmes." - Jamie Zawinski, 1997
Moshe Katz
5
@MosheKatz Bien que je puisse comprendre que les gens aiment parler de cette citation - et les gens utilisent certainement des expressions régulières pour des choses totalement inappropriées, mais ce n'est pas l'une d'entre elles. Dans le cas fourni par l'OP, non seulement cela correspond aux critères, mais aussi très bien. Les expressions régulières ne sont pas intrinsèquement mauvaises, et la correspondance des chaînes avec des paramètres bien définis est ce pour quoi elle est faite.
Thor84no
3
@ Thor84no Habituellement, je suppose que le questionneur ne tente pas réellement de faire correspondre un exemple aussi artificiel que le premier cas, et que les correspondances dans le monde réel ne sont pas si simples, auquel cas je ne pense pas qu'un RegEx va être la bonne façon de le faire. Pour le dire autrement, si votre RegEx est juste une liste d'options séparées par des barres verticales, il n'est pas plus lisible que toutes les autres suggestions, et peut-être beaucoup moins efficace.
Moshe Katz
10

Excellentes réponses, mais vous pourriez rendre le code beaucoup plus lisible en enveloppant l'un d'entre eux dans une fonction.

Ceci est complexe si la déclaration, lorsque vous (ou quelqu'un d'autre) lisez le code dans un an, vous allez parcourir pour trouver la section pour comprendre ce qui se passe. Une déclaration avec ce niveau de logique métier vous fera trébucher pendant quelques secondes pendant que vous travaillez sur ce que vous testez. Où en tant que code comme celui-ci, vous permettra de continuer la numérisation.

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

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

Nommez votre fonction explicitement pour que ce que vous testez soit immédiatement évident et que votre code soit beaucoup plus facile à analyser et à comprendre.

Fran Hoey
la source
1
Meilleure réponse que j'ai vue ici. Vraiment, je vois que les gens ne se soucient pas des principes d'un bon design. Je veux juste réparer quelque chose rapidement et oublier d'améliorer le code pour que le système soit maintenable à l'avenir!
Maykonn
Que diriez-vous simplement de commenter // CheckIfBusinessRuleIsTrue?
daniel1426
4

Vous pouvez mettre toutes les réponses dans un ensemble Javascript , puis simplement appeler .contains()l'ensemble.

Vous devez toujours déclarer tout le contenu, mais l'appel en ligne sera plus court.

Quelque chose comme:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
Guido Anselmi
la source
4
Cela semble être une façon incroyablement inutile d'accomplir ce que le PO tente de faire. Ainsi, bien que vous puissiez inclure une bibliothèque tierce supplémentaire, instancier un objet et appeler une méthode dessus, vous ne devriez probablement pas.
KaptajnKold
@Captain Cold: Eh bien, l'OP a demandé la concision et non l'empreinte mémoire. Peut-être que l'ensemble pourrait être réutilisé pour d'autres opérations?
Guido Anselmi
1
Bien sûr, mais même si: Est- ce que vous en toute honnêteté jamais faire vous-même? Si jamais je voyais cela dans la nature, je le considérerais comme un WTF majeur.
KaptajnKold
1
Ouais vous avez raison (je vous ai donné les + 1) mais cela suppose que cette vérification ne se fait nulle part ailleurs. Si cela est fait à plusieurs autres endroits et / ou si le test change, alors l'utilisation de l'ensemble peut avoir un sens. Je laisse à l'OP le soin de choisir la meilleure solution. Cela dit, s'il s'agissait d'un usage solitaire, je conviendrais que l'utilisation de l'ensemble mériterait le chapeau de honte As Clown.
Guido Anselmi
2
Je pense en fait que la réponse choisie est absolument horrible et pire que l'utilisation de l'ensemble car elle est absolument illisible.
Guido Anselmi
2

L'une de mes façons préférées d'y parvenir est d'utiliser une bibliothèque telle que 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
la source
1
containsest sans doute une meilleure solution quesome
Dennis
1
Pas besoin d'utiliser une librairie pour cela: somec'est une fonction sur le prototype Array dans EC5.
KaptajnKold
2
C'est vrai, mais tout le monde n'a pas le support EC5 disponible. De plus, j'aime vraiment le soulignement. :)
jcreamer898
Si vous utilisez déjà une bibliothèque comme le trait de soulignement, c'est probablement le moyen le plus simple. Sinon, cela n'a pas de sens de charger une bibliothèque entière juste pour une seule fonction.
Moshe Katz
2

une autre façon ou une autre façon géniale que j'ai trouvée est celle-ci ...

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

bien sûr, comme vous pouvez le voir, cela va encore plus loin et les rend faciles à suivre la logique.

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

en utilisant des opérateurs tels que ~ && || ((), ()) ~~ ne convient que si votre code se rompt plus tard. Vous ne saurez pas par où commencer. La lisibilité est donc GRANDE.

si vous le devez, vous pourriez le raccourcir.

('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);

et si vous voulez faire l'inverse

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

Utilisez simplement une switchinstruction au lieu d'une ifinstruction:

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 fonctionne également plus rapidement que de comparer de nombreuses conditions dans un if

unmultimedio
la source
2

Pour de très longues listes de chaînes, cette idée permettrait d'économiser quelques caractères (sans dire que je le recommanderais dans la vraie vie, mais cela devrait fonctionner).

Choisissez un caractère dont vous savez qu'il n'apparaîtra pas dans votre test.type, utilisez-le comme délimiteur, collez-les tous dans une longue chaîne et recherchez que:

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

Si vos chaînes sont davantage contraintes, vous pouvez même omettre les délimiteurs ...

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

... mais vous devrez faire attention aux faux positifs dans ce cas (par exemple, "embite" correspondrait dans cette version)

CupawnTae
la source
2

Pour la lisibilité, créez une fonction pour le test (oui, une fonction sur une ligne):

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

alors appelez-le:


    if (isTypeDefined(test)) {

}
...
zaph
la source
1

Je pense qu'il y a 2 objectifs lors de l'écriture de ce type de condition if.

  1. brièveté
  2. lisibilité

En tant que tel, parfois le n ° 1 peut être le plus rapide, mais je prendrai le n ° 2 pour une maintenance facile plus tard. Selon le scénario, j'opterai souvent pour une variante de la réponse de Walter.

Pour commencer, j'ai une fonction disponible globalement dans le cadre de ma bibliothèque existante.

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

et ensuite, lorsque je veux réellement exécuter une condition if similaire à la vôtre, je crée un objet avec une liste des valeurs valides:

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

Ce n'est pas aussi rapide qu'une instruction switch / case et un peu plus verbeux que certains des autres exemples, mais je reçois souvent une réutilisation de l'objet ailleurs dans le code, ce qui peut être très pratique.

En utilisant l'un des échantillons jsperf ci-dessus, j'ai ajouté ce test et une variation pour comparer les vitesses. http://jsperf.com/if-statements-test-techsin/6 La chose la plus intéressante que j'ai notée est que certains combos de tests dans Firefox sont beaucoup plus rapides que même Chrome.

scunliffe
la source
1

Cela peut être résolu avec une simple boucle for:

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

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

Nous utilisons la première section de la boucle for pour initialiser les arguments que vous souhaitez mettre en correspondance, la deuxième section pour arrêter l'exécution de la boucle for et la troisième section pour que la boucle se termine finalement.


la source