Cómo acortar mis declaraciones condicionales

154

Tengo una declaración condicional muy larga como la siguiente:

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

Me preguntaba si podría refactorizar esta expresión / declaración en una forma más concisa.

¿Alguna idea sobre cómo lograr esto?

Gato volador
fuente
23
¿Puedes ponerlos en una matriz y usarlos in?
jeremy
ahora solo si alguien pudiera comprobar cuál es el más rápido
Muhammad Umer
3
¡Esto puede ser un shock para todos, pero lo que OP tiene es un claro ganador en velocidad! Quizás haga que el navegador optimice mucho esto. Resultados: (1) si con ||. (2) switchdeclaraciones. (3) expresión regular. (4) ~. jsperf.com/if-statements-test-techsin
Muhammad Umer
3
También puede estar abordando esto de la manera incorrecta. En este caso, esos 4 tipos tienen algo en común. ¿Qué es? Si llevamos esto a un caso más extremo, ¿qué pasaría si necesitáramos agregar 10 tipos más para que coincida con esta condición? O 100? Si hubiera más, probablemente no consideraría usar esta solución, ni ninguna de las otras sugeridas. Estás viendo una gran declaración if como esta, y piensas que es un olor a código, lo cual es una buena señal. Su mejor manera de hacerlo más conciso sería si pudiera escribir if (test.your_common_condition). Es más fácil de entender en este contexto y más extensible.
gmacdougall

Respuestas:

241

Coloque sus valores en una matriz y verifique si su artículo está en la matriz:

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

Si un navegador que admite no tiene el Array#includesmétodo, puede usar este polyfill .


Breve explicación del ~acceso directo tilde:

Actualización: dado que ahora tenemos el includesmétodo, ya no tiene sentido usar el ~hack. Simplemente mantener esto aquí para las personas que están interesadas en saber cómo funciona y / o lo han encontrado en el código de otros.

En lugar de verificar si el resultado indexOfes >= 0, hay un pequeño atajo:

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

Aquí está el violín: http://jsfiddle.net/HYJvK/

¿Como funciona esto? Si se encuentra un elemento en la matriz, indexOfdevuelve su índice. Si no se encontró el artículo, volverá -1. Sin entrar en demasiados detalles, ~es un operador NOT bit a bit , que regresará 0solo por -1.

Me gusta usar el ~acceso directo, ya que es más sucinto que hacer una comparación del valor de retorno. Desearía que JavaScript tuviera una in_arrayfunción que devuelva un booleano directamente (similar a PHP), pero eso es solo una ilusión ( Actualización: ahora lo hace. Se llama includes. Ver arriba). Tenga en cuenta que jQuery inArray, aunque comparte la firma del método PHP, en realidad imita la indexOffuncionalidad nativa (que es útil en diferentes casos, si el índice es lo que realmente busca).

Nota importante: el uso del acceso directo tilde parece estar envuelto en controversia, ya que algunos creen con vehemencia que el código no es lo suficientemente claro y debe evitarse a toda costa (vea los comentarios sobre esta respuesta). Si comparte su sentimiento, debe apegarse a la .indexOf(...) >= 0solución.


Una pequeña explicación más larga:

Los enteros en JavaScript están firmados, lo que significa que el bit más a la izquierda está reservado como el bit de signo; una bandera para indicar si el número es positivo o negativo, con un 1ser negativo.

Aquí hay algunos números positivos de muestra en formato binario de 32 bits:

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

Ahora aquí están esos mismos números, pero negativos:

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

¿Por qué combinaciones tan extrañas para los números negativos? Sencillo. Un número negativo es simplemente el inverso del número positivo + 1; agregar el número negativo al número positivo siempre debería rendir 0.

Para entender esto, hagamos algo de aritmética binaria simple.

Aquí es cómo agregaríamos -1a +1:

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

Y así es como agregaríamos -15a +15:

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

¿Cómo obtenemos esos resultados? Al sumar regularmente, la forma en que nos enseñaron en la escuela: comienzas en la columna de la derecha y sumas todas las filas. Si la suma es mayor que el mayor número de un solo dígito (que en decimal es9 , pero en binario es 1), llevamos el resto a la siguiente columna.

Ahora, como notará, al agregar un número negativo a su número positivo, la columna más a la derecha que no es todo 0s siempre tendrá dos 1s, que al sumarse resultarán 2. La representación binaria de dos seres 10, que lleva la 1a la siguiente columna, y puso una 0para el resultado de la primera columna. Todas las demás columnas a la izquierda tienen solo una fila con a 1, por lo que el contenido 1de la columna anterior volverá a sumarse 2, lo que luego se transferirá ... Este proceso se repite hasta llegar a la columna más a la izquierda, donde el1 ser transportado no tiene a dónde ir, por lo que se desborda y se pierde, y nos queda con 0s por todas partes.

Este sistema se llama Complemento de 2 . Puedes leer más sobre esto aquí:

Representación del complemento 2 para enteros firmados .


Ahora que el curso intensivo en el complemento de 2 ha terminado, notará que -1es el único número cuya representación binaria está 1en todas partes.

Utilizando el ~bit a bit NO operador, todos los bits de un número dado se invierten. La única forma de 0volver de invertir todos los bits es si comenzamos con 1todo.

Entonces, todo esto fue una forma larga de decir que ~nsolo volverá 0si nes así -1.

Joseph Silber
fuente
59
Si bien el uso de operadores bit a bit seguro es atractivo, ¿es realmente mejor que !== -1de alguna manera concebible? ¿No es la lógica booleana explícita más apropiada que usar implícitamente la falsedad de cero?
Phil
21
Muy bien, pero no me gusta. A primera vista, no está claro qué está haciendo el código, lo que lo hace imposible de mantener. Prefiero la respuesta de "Yuriy Galanter".
Jon Rea
65
-1 nuevos programadores ven respuestas como esta, piensan que es una forma genial y aceptable de codificar, luego en 5 años tengo que mantener su código y arrancarme el pelo
BlueRaja - Danny Pflughoeft
23
Este idioma definitivamente no es común en lenguajes como C #, Java o Python, que son mis áreas de especialización. Y solo pregunté a algunos de los expertos locales de Javascript aquí, y ninguno de ellos lo había visto antes; así que claramente no es tan común como usted afirma. Siempre debe evitarse en favor de lo mucho más claro y más común != -1.
BlueRaja - Danny Pflughoeft
12
-1 debido a juegos de bits innecesarios en un lenguaje que realmente no especifica mucho acerca de las representaciones de bits en primer lugar. Además, la mayor parte de esta respuesta está explicando los juegos de bits. Si tienes que escribir 20 párrafos para explicar el truco, ¿REALMENTE te ahorra tiempo?
esponjoso
242

Puede usar la instrucción switch con fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
Yuriy Galanter
fuente
9
es básicamente lo mismo que si, el índice del método de matriz es mucho mejor
NimChimpsky
66
@kojiro es tristemente, la respuesta correcta es esta, pero es imposible llamar la atención sobre esto en lugar del increíble truco de bitwhise-array.
Manu343726
1
Sabía que esta respuesta tenía que estar aquí, pero tenía que desplazarme hasta el fondo para encontrarla. Es exactamente para lo que se diseñó la declaración de cambio y se traslada a muchos otros idiomas. He encontrado que mucha gente no sabe sobre el método de 'caída' en una declaración de cambio.
Jimmy Johnson
3
Esta solución es la más rápida en Firefox y Safari y la segunda más rápida (después de la original ||) en Chrome. Ver jsperf.com/if-statements-test-techsin
pabouk
3
Creo que sería horrible escribir en un método si tuvieras muchas condiciones ... Sería bueno por algunas condiciones, pero por más de 10 iría por la matriz para mantener el código ordenado.
yu_ominae
63

Usando Science: debe hacer lo que idfah dijo y esto para la velocidad más rápida mientras mantiene el código corto:

ESTO ES MÁS RÁPIDO QUE EL ~MÉTODO

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

http://jsperf.com/if-statements-test-techsin ingrese la descripción de la imagen aquí (Conjunto superior: Chrome, conjunto inferior: Firefox)

Conclusión

Si posibilidades son pocas y sabe que ciertas personas tienen más probabilidades de ocurrir que se obtiene el máximo rendimiento if ||, switch fall throughy if(obj[keyval]).

Si las posibilidades son muchas , y cualquiera de ellas podría ser la más frecuente, en otras palabras, no puede saber cuál es más probable que ocurra que obtener el mayor rendimiento de la búsqueda de objetos if(obj[keyval])y regexsi eso encaja.

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

actualizaré si surge algo nuevo.

Muhammad Umer
fuente
2
¡+1 para una muy buena publicación! Si entiendo correctamente, ¿cuál switch casees el método más rápido?
user1477388
1
en Firefox sí, en Chrome esif ( ...||...||...)...
Muhammad Umer
8
Es más rápido si realmente hace muchos bucles sobre esta entrada, pero es mucho más lento si tiene un bucle con n muy grande (número de cadenas "itemX"). Pirateé este generador de código que puedes usar para verificar (o quizás refutar). obj["itemX"]es extremadamente rápido si n es grande. Básicamente, lo rápido depende del contexto. Que te diviertas.
kojiro
3
Entonces es el método más rápido, pero ¿importa ?
congusbongus
1
@Mich No sacrifiques la elegancia del código solo por la velocidad. Esto es lo que muchas personas te dirán. Al final, solo usa el sentido común.
Andre Figueiredo
32

Si está comparando con cadenas y hay un patrón, considere usar expresiones regulares.

De lo contrario, sospecho que intentar acortarlo solo ofuscará su código. Considere simplemente envolver las líneas para que quede bonito.

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
idfah
fuente
44
esta respuesta es la ganadora en términos de velocidad jsperf.com/if-statements-test-techsin
Muhammad Umer
1
Esta también es la más fácil de expandir cuando el proyecto entra en modo de mantenimiento (con reglas como, (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

Usar un objeto como una matriz asociativa es algo bastante común, pero dado que JavaScript no tiene un conjunto nativo, también puede usar objetos como conjuntos baratos.

kojiro
fuente
¿Cómo es esto más corto de lo normal si la declaración de que FlyingCat está tratando de acortar?
dcarson el
1
ifEl condicional de la declaración de @dcarson OP toma 78 caracteres si elimina todos los espacios en blanco. El mío tiene 54 si se escribe así: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. Básicamente, usa cuatro caracteres por cada dos minas para cada tecla adicional.
kojiro
1
pero puedes hacer: if (posibilidades [test.type]) y guardar un total de 2 caracteres! :)
dc5
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

o si los artículos no son tan uniformes, entonces:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Mate
fuente
9
"Algunas personas, cuando se enfrentan a un problema, piensan 'Lo sé, usaré expresiones regulares'". Ahora ellos tienen dos problemas." - Jamie Zawinski, 1997
Moshe Katz
55
@MosheKatz Si bien puedo entender que a la gente le gusta hablar de esa cita, y la gente ciertamente usa expresiones regulares para cosas totalmente inadecuadas, pero esta no es una de ellas. En el caso proporcionado por el OP, esto no solo coincide con los criterios, sino que también lo hace muy bien. Las expresiones regulares no son intrínsecamente malvadas, y para lo que está hecha se combinan cadenas con parámetros bien definidos.
Thor84no
3
@ Thor84no Por lo general, supondría que el interlocutor no está intentando realmente comparar con un ejemplo tan ingenioso como el primer caso, y que las coincidencias del mundo real no son tan simples, en cuyo caso no creo que vaya un RegEx para ser la forma correcta de hacerlo. Para decirlo de otra manera, si su RegEx es solo una lista de opciones separadas por caracteres de canalización, no es más legible que ninguna de las otras sugerencias, y posiblemente significativamente menos eficiente.
Moshe Katz
10

Excelentes respuestas, pero podría hacer que el código sea mucho más legible envolviendo uno de ellos en una función.

Esto es complejo si la declaración, cuando usted (u otra persona) lea el código dentro de unos años, estará escaneando para encontrar la sección para comprender lo que está sucediendo. Una declaración con este nivel de lógica empresarial le hará tropezar durante unos segundos mientras trabaja en lo que está probando. Donde un código como este, le permitirá continuar escaneando.

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

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

Asigne un nombre explícito a su función para que sea inmediatamente obvio lo que está probando y su código será mucho más fácil de escanear y comprender.

Fran Hoey
fuente
1
La mejor respuesta que he visto aquí. Realmente, veo que a la gente no le importan los principios del buen diseño. ¡Solo quiere arreglar algo rápidamente y olvidarse de mejorar el código para que el sistema sea mantenible en el futuro!
Maykonn
¿Qué tal solo comentar como // CheckIfBusinessRuleIsTrue?
daniel1426
4

Puede poner todas las respuestas en un conjunto de Javascript y luego simplemente llamar .contains()al conjunto.

Aún debe declarar todos los contenidos, pero la llamada en línea será más corta.

Algo como:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
Guido Anselmi
fuente
44
Parece una manera increíblemente derrochadora de lograr lo que el OP está tratando de hacer. Entonces, si bien podría incluir una biblioteca adicional de terceros, instanciar un objeto y llamar a un método, probablemente no debería hacerlo.
KaptajnKold
@ Capitán Frío: Bueno, el OP pidió concisión, no huella de memoria. ¿Quizás el conjunto podría reutilizarse para otras operaciones?
Guido Anselmi
1
Seguro, pero aún así: ¿Está, en honor a la verdad nunca hace usted mismo? Si alguna vez viera esto en la naturaleza, lo consideraría un gran WTF.
KaptajnKold
1
Sí, tienes razón (te di los +1), pero se supone que esta verificación no se realiza en ningún otro lado. Si se está haciendo en varios otros lugares y / o la prueba cambia, entonces el uso del Conjunto podría tener sentido. Le dejo al OP que elija la mejor solución. Todo lo dicho si este fuera un uso solitario, estaría de acuerdo en que usar el Set merecería el sombrero de vergüenza As Clown.
Guido Anselmi
2
De hecho, creo que la respuesta elegida es absolutamente horrible y peor que usar el Set, ya que es absolutamente ilegible.
Guido Anselmi
2

Una de mis formas favoritas de lograr esto es con una 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
fuente
1
containses posiblemente una mejor solución quesome
Dennis
1
No es necesario usar una biblioteca para esto: somees una función en el prototipo de matriz en EC5.
KaptajnKold
2
Es cierto, pero no todos tienen soporte EC5 disponible. Además, realmente me gusta el guión bajo. :)
jcreamer898
Si usted está ya utilizando una biblioteca como subrayado, esta es probablemente la forma más fácil. De lo contrario, tiene poco sentido cargar una biblioteca completa solo para la función.
Moshe Katz
2

otra forma u otra forma increíble que encontré es esto ...

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

Por supuesto, como puede ver, esto lleva las cosas un paso más allá y facilita la lógica de seguimiento.

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

utilizando operadores como ~ && || ((), ()) ~~ está bien solo si su código se rompe más adelante. No sabrás por dónde empezar. Entonces la legibilidad es GRANDE.

si es necesario, puedes hacerlo más corto.

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

y si quieres hacer inversa

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

Simplemente use una switchdeclaración en lugar de una ifdeclaración:

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 también funciona más rápido que comparar muchos condicionales dentro de un if

unmultimedio
fuente
2

Para listas muy largas de cadenas, esta idea ahorraría algunos caracteres (sin decir que lo recomendaría en la vida real, pero debería funcionar).

Elija un carácter que sepa que no aparecerá en su tipo de prueba, úselo como delimitador, péguelos en una cadena larga y busque eso:

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

Si sus cadenas están más restringidas, incluso podría omitir los delimitadores ...

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

... pero tendría que tener cuidado con los falsos positivos en ese caso (por ejemplo, "embite" coincidiría en esa versión)

CupawnTae
fuente
2

Para facilitar la lectura, cree una función para la prueba (sí, una función de una línea):

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

entonces llámalo:


    if (isTypeDefined(test)) {

}
...
zaph
fuente
1

Creo que hay 2 objetivos al escribir este tipo de condición if.

  1. brevedad
  2. legibilidad

Como tal, a veces el n. ° 1 puede ser el más rápido, pero tomaré el n. ° 2 para facilitar el mantenimiento más adelante. Dependiendo del escenario, a menudo optaré por una variación de la respuesta de Walter.

Para comenzar, tengo una función disponible globalmente como parte de mi biblioteca existente.

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

y luego, cuando realmente quiero ejecutar una condición if similar a la suya, crearía un objeto con una lista de los valores válidos:

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

No es tan rápido como una declaración de cambio / caso y un poco más detallado que algunos de los otros ejemplos, pero a menudo obtengo la reutilización del objeto en otra parte del código que puede ser bastante útil.

Piggybacking en una de las muestras jsperf hechas anteriormente. Agregué esta prueba y una variación para comparar velocidades. http://jsperf.com/if-statements-test-techsin/6 Lo más interesante que noté es que ciertos combos de prueba en Firefox son mucho más rápidos que incluso Chrome.

scunliffe
fuente
1

Esto se puede resolver con un simple bucle for:

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

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

Usamos la primera sección del ciclo for para inicializar los argumentos que desea hacer coincidir, la segunda sección para detener la ejecución del ciclo for y la tercera sección para hacer que el ciclo finalmente salga.


fuente