Jak skrócić moje instrukcje warunkowe

154

Mam bardzo długą instrukcję warunkową, taką jak ta:

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

Zastanawiałem się, czy mógłbym zmienić to wyrażenie / oświadczenie w bardziej zwięzłą formę.

Masz pomysł, jak to osiągnąć?

FlyingCat
źródło
23
Możesz umieścić je w tablicy i użyć in?
jeremy
teraz tylko gdyby ktoś mógł sprawdzić, który z nich jest najszybszy
Muhammad Umer
3
to może szok dla wszystkich, ale to, co ma OP, jest wyraźnym zwycięzcą w szybkości !!!!!!! Może powodować częstą optymalizację przeglądarki. Wyniki: (1) jeśli z ||. (2) switchoświadczenia. (3) wyrażenie regularne. (4) ~. jsperf.com/if-statements-test-techsin
Muhammad Umer
3
Możesz też podchodzić do tego w niewłaściwy sposób. W tym przypadku te 4 typy mają coś wspólnego. Co to jest? Jeśli weźmiemy to do bardziej ekstremalnego przypadku, co by było, gdybyśmy musieli dodać 10 kolejnych typów, aby spełnić ten warunek. Lub 100? Gdyby było ich więcej, prawdopodobnie nie rozważałbyś skorzystania z tego rozwiązania ani żadnego z innych sugerowanych. Widzisz takie duże stwierdzenie if i myślisz, że to zapach kodu, co jest dobrym znakiem. Najlepszym sposobem, aby uczynić to bardziej zwięzłym, byłoby napisanie if (test.your_common_condition). W tym kontekście jest łatwiejszy do zrozumienia i bardziej rozszerzalny.
gmacdougall,

Odpowiedzi:

241

Umieść swoje wartości w tablicy i sprawdź, czy pozycja znajduje się w tablicy:

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

Jeśli obsługiwana przeglądarka nie ma tej Array#includesmetody, możesz użyć tego wypełnienia .


Krótkie wyjaśnienie ~skrótu tyldy:

Aktualizacja: Ponieważ mamy teraz includesmetodę, nie ma już sensu używać ~hackowania. Po prostu zachowaj to tutaj dla ludzi, którzy są zainteresowani wiedzą, jak to działa i / lub napotkali to w kodzie innych.

Zamiast sprawdzać, czy wynik indexOf jest >= 0, istnieje ładny mały skrót:

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

Oto skrzypce: http://jsfiddle.net/HYJvK/

Jak to działa? Jeśli element zostanie znaleziony w tablicy, indexOfzwraca jego indeks. Jeśli przedmiot nie został znaleziony, wróci -1. Bez wchodzenia w zbyt wiele szczegółów ~jest operatorem bitowym NOT , który zwróci0 tylko dla -1.

Lubię używać ~skrótu, ponieważ jest bardziej zwięzły niż porównanie wartości zwracanej. Chciałbym, żeby JavaScript miał in_arrayfunkcję, która zwraca bezpośrednio wartość logiczną (podobną do PHP), ale to tylko pobożne życzenia ( aktualizacja: teraz ma. Nazywa się includes. Patrz powyżej). Zauważ, że jQuery inArray, podczas udostępniania sygnatury metody PHP, w rzeczywistości naśladuje natywną indexOffunkcjonalność (co jest przydatne w różnych przypadkach, jeśli indeks jest tym, czego naprawdę szukasz).

Ważna uwaga: używanie skrótu tyldy wydaje się być przedmiotem kontrowersji, jak niektórzy zaciekle uważają, że kod nie jest wystarczająco jasny i należy go unikać za wszelką cenę (patrz komentarze do tej odpowiedzi). Jeśli podzielasz ich zdanie, powinieneś trzymać się .indexOf(...) >= 0rozwiązania.


Trochę dłuższe wyjaśnienie:

Liczby całkowite w JavaScript są podpisywane, co oznacza, że ​​skrajny lewy bit jest zarezerwowany jako bit znaku; flaga wskazująca, czy liczba jest dodatnia czy ujemna, z a1 czym jest ujemna.

Oto kilka przykładowych liczb dodatnich w 32-bitowym formacie binarnym:

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

Oto te same liczby, ale ujemne:

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

Skąd takie dziwne kombinacje liczb ujemnych? Prosty. Liczba ujemna jest po prostu odwrotnością liczby dodatniej + 1; dodanie liczby ujemnej do liczby dodatniej powinno zawsze dawać0 .

Aby to zrozumieć, zróbmy prostą arytmetykę binarną.

Oto, jak dodalibyśmy -1do +1:

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

A oto jak dodalibyśmy -15do +15:

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

Jak otrzymujemy te wyniki? Robiąc regularne dodawanie, tak jak nas uczono w szkole: zaczynasz od skrajnej prawej kolumny i dodajesz wszystkie wiersze. Jeśli suma jest większa niż największa liczba jednocyfrowa (która w systemie dziesiętnym to 9, ale w systemie dwójkowym to1 ), resztę przenosimy do następnej kolumny.

Teraz, jak zauważysz, po dodaniu liczby ujemnej do jej liczby dodatniej, skrajna prawa kolumna, która nie zawiera wszystkich 0s, zawsze będzie miała dwa 1s, co po dodaniu da w wyniku 2. Binarną reprezentację dwóch bytów 10przenosimy 1do następnej kolumny i umieszczamy jako 0wynik w pierwszej kolumnie. Wszystkie inne kolumny po lewej mają tylko jeden wiersz z literą a 1, więc 1przeniesione z poprzedniej kolumny ponownie sumują się do 2, co następnie przenosi ... Ten proces powtarza się, aż dojdziemy do skrajnej lewej kolumny, gdzie to, co 1ma zostać przeniesione, nie ma dokąd pójść, więc przepełnia się i gubi, a my zostajemy z nami0 s na całej długości.

Ten system nazywa się dopełnieniem 2 . Możesz przeczytać więcej na ten temat tutaj:

Reprezentacja uzupełnienia 2 dla podpisanych liczb całkowitych .


Teraz, gdy błyskawiczny kurs dopełniania 2 się skończył, zauważysz, że -1jest to jedyna liczba, której reprezentacja binarna to1 całą szerokość.

Używając ~bitowego operatora NOT, wszystkie bity w danej liczbie są odwracane. Jedynym sposobem 0powrotu do odwracania wszystkich bitów jest rozpoczęcie od1 wszystkich.

Więc to wszystko było rozwlekłym sposobem stwierdzenia, że ~npowróci tylko 0wtedy, gdy tak njest -1.

Joseph Silber
źródło
59
Chociaż używanie operatorów bitowych z pewnością jest sexy, czy naprawdę jest lepsze niż !== -1w jakikolwiek możliwy sposób? Czy jawna logika boolowska nie jest bardziej odpowiednia niż niejawne użycie błędu zero?
Phil
21
Niezła technika, ale mi się to nie podoba. Na pierwszy rzut oka nie jest jasne, co robi kod, co sprawia, że ​​jest nie do utrzymania. O wiele bardziej wolę odpowiedź „Yuriy Galanter”.
Jon Rea,
65
-1 nowi programiści widzą takie odpowiedzi, myślą, że to fajny i akceptowalny sposób kodowania, a potem za 5 lat muszę utrzymywać ich kod i wyrywać sobie włosy
BlueRaja - Danny Pflughoeft
23
Ten idiom zdecydowanie nie jest powszechny w językach takich jak C #, Java czy Python, które są moją specjalnością. I właśnie zapytałem kilku lokalnych ekspertów od Javascript, a żaden z nich nigdy wcześniej tego nie widział; więc nie jest to tak powszechne, jak twierdzisz. Zawsze należy go unikać na rzecz dużo jaśniejszych i powszechniejszych != -1.
BlueRaja - Danny Pflughoeft
12
-1 ze względu na niepotrzebne bitfiddling w języku, który przede wszystkim nie określa zbyt wiele na temat reprezentacji bitów. Poza tym większość tej odpowiedzi wyjaśnia bitfiddling. Jeśli musisz napisać 20 akapitów, aby wyjaśnić włamanie, czy NAPRAWDĘ oszczędza to czas?
puszysty
242

Możesz użyć instrukcji switch z fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
Yuriy Galanter
źródło
9
jest w zasadzie taki sam jak if, indeks metody tablicowej jest znacznie lepszy
NimChimpsky
6
@kojiro to niestety właściwa odpowiedź, ale nie można zwrócić na to uwagi zamiast na niesamowitą sztuczkę z tablicą bitwhise.
Manu343726
1
Wiedziałem, że ta odpowiedź musi tu być, ale musiałem przewinąć w dół, aby ją znaleźć. Właśnie do tego celu została zaprojektowana instrukcja przełącznika i jest ona przenoszona na wiele innych języków. Zauważyłem, że wiele osób nie wie o metodzie „upadku” w instrukcji przełącznika.
Jimmy Johnson
3
To rozwiązanie jest najszybsze w Firefoksie i Safari i drugie najszybsze (po oryginale ||) w Chrome. Zobacz jsperf.com/if-statements-test-techsin
pabouk
3
Myślę, że pisanie w metodzie byłoby okropne, gdybyś miał wiele warunków ... Byłoby dobrze dla kilku warunków, ale dla więcej niż 10 wybrałbym tablicę, aby zachować porządek w kodzie.
yu_ominae
63

Korzystanie z nauki: powinieneś zrobić to, co powiedział idfah, a to dla największej szybkości przy zachowaniu krótkiego kodu:

TO SZYBCIEJ NIŻ ~METODA

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

http://jsperf.com/if-statements-test-techsin wprowadź opis obrazu tutaj (górny zestaw: Chrome, dolny zestaw: Firefox)

Wniosek:

Jeśli możliwości są nieliczne i wiesz, że niektóre z nich są bardziej prawdopodobne niż można uzyskać maksymalną wydajność if ||, switch fall throughoraz if(obj[keyval]).

Jeśli możliwości jest wiele , a każda z nich może być najbardziej występująca, innymi słowy, nie możesz wiedzieć, która z nich jest najbardziej prawdopodobna, niż uzyskujesz największą wydajność z wyszukiwania obiektów if(obj[keyval])i regexczy to pasuje.

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

zaktualizuję, jeśli pojawi się coś nowego.

Muhammad Umer
źródło
2
+1 za naprawdę dobry post! Jeśli dobrze rozumiem, jaka switch casejest najszybsza metoda?
user1477388
1
w firefox tak, w chrome toif ( ...||...||...)...
Muhammad Umer
8
Jest szybsze, jeśli naprawdę wykonujesz wiele pętli na tym wejściu, ale jest znacznie wolniejsze, jeśli masz jedną pętlę z bardzo dużym n (liczbą ciągów „itemX”). Zhakowałem ten generator kodu , którego możesz użyć do weryfikacji (lub może obalić). obj["itemX"]jest niezwykle szybki, jeśli n jest duże. Zasadniczo to, co jest szybkie, zależy od kontekstu. Baw się dobrze.
kojiro
3
Więc to najszybsza metoda, ale czy to ma znaczenie ?
congusbongus
1
@Mich Nie poświęcaj elegancji kodu tylko dla szybkości. Tak powie wiele osób. W końcu po prostu kieruj się zdrowym rozsądkiem.
Andre Figueiredo
32

Jeśli porównujesz ciągi i istnieje wzorzec, rozważ użycie wyrażeń regularnych.

W przeciwnym razie podejrzewam, że próba skrócenia go spowoduje tylko zaciemnienie kodu. Rozważ po prostu zawinięcie linii, aby było ładnie.

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
idfah
źródło
4
ta odpowiedź jest zwycięzcą pod względem prędkości jsperf.com/if-statements-test-techsin
Muhammad Umer
1
Jest to również najłatwiejsze do rozbudowy, gdy projekt przechodzi w tryb konserwacji (z regułami takimi jak, (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

Używanie obiektu jako tablicy asocjacyjnej jest dość powszechne, ale ponieważ JavaScript nie ma natywnego zestawu, możesz używać obiektów jako tanich zestawów.

kojiro
źródło
Jak to jest krótsze niż normalne, jeśli stwierdzenie, które FlyingCat próbuje skrócić?
dcarson,
1
Warunek ifinstrukcji @dcarson OP zajmuje 78 znaków, jeśli usuniesz wszystkie białe znaki. Kopalnia zajmuje 54 jeśli piszesz to tak: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. Zasadniczo używa czterech znaków na każde dwa użycie min dla każdego dodatkowego klucza.
kojiro,
1
ale możesz zrobić: if (możliwości [test.type]) i zapisać całe 2 znaki! :)
dc5,
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

lub jeśli przedmioty nie są tak jednolite, to:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Matt
źródło
9
„Niektórzy ludzie w obliczu problemu myślą:„ Wiem, użyję wyrażeń regularnych ”. Teraz mają dwa problemy. " - Jamie Zawinski, 1997
Moshe Katz
5
@MosheKatz Chociaż rozumiem, że ludzie lubią się bawić tym cytatem - i ludzie z pewnością używają wyrażeń regularnych do zupełnie nieodpowiednich rzeczy, ale to nie jest jedna z nich. W przypadku przedstawionym przez PO nie tylko spełnia to kryteria, ale robi to bardzo dobrze. Wyrażenia regularne nie są z natury złe, a pasujące łańcuchy o dobrze zdefiniowanych parametrach są tym, do czego są stworzone.
Thor84no
3
@ Thor84no Zwykle zakładam, że osoba pytająca w rzeczywistości nie próbuje dopasować się do tak wymyślnego przykładu, jak w pierwszym przypadku, i że dopasowania w świecie rzeczywistym nie są takie proste, w takim przypadku nie sądzę, aby regEx był być właściwym sposobem, aby to zrobić. Innymi słowy, jeśli twoje wyrażenie regularne jest tylko listą opcji oddzielonych pionową kreską, nie jest bardziej czytelne niż inne sugestie i prawdopodobnie znacznie mniej wydajne.
Moshe Katz
10

Doskonałe odpowiedzi, ale możesz uczynić kod znacznie bardziej czytelnym, opakowując jedną z nich w funkcję.

Jest to skomplikowane, jeśli stwierdzisz, że kiedy ty (lub ktoś inny) przeczytasz kod za lata, będziesz skanować, aby znaleźć sekcję, aby zrozumieć, co się dzieje. Stwierdzenie z takim poziomem logiki biznesowej spowoduje, że przez kilka sekund będziesz się potykać, gdy będziesz analizować to, co testujesz. Gdzie taki kod, pozwoli ci kontynuować skanowanie.

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

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

Nazwij swoją funkcję jawnie, aby od razu było wiadomo, co testujesz, a kod będzie znacznie łatwiejszy do zeskanowania i zrozumienia.

Fran Hoey
źródło
1
Najlepsza odpowiedź, jaką tu widziałem. Naprawdę widzę, że ludzie nie przejmują się zasadami dobrego projektowania. Po prostu chcę szybko coś naprawić i zapomnieć o ulepszaniu kodu, aby system był łatwy do utrzymania w przyszłości!
Maykonn
A może po prostu skomentujesz coś podobnego // CheckIfBusinessRuleIsTrue?
daniel1426
4

Możesz umieścić wszystkie odpowiedzi w zestawie Javascript, a następnie po prostu zadzwonić .contains()do zestawu.

Nadal musisz zadeklarować całą zawartość, ale wywołanie inline będzie krótsze.

Coś jak:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
Guido Anselmi
źródło
4
To wydaje się niesamowicie marnotrawnym sposobem na osiągnięcie tego, co próbuje zrobić PO. Tak więc, podczas gdy mogłyby zawierać dodatkową bibliotekę 3rd partii, instancję obiektu i wywołać metodę na nim, to prawdopodobnie nie powinno.
KaptajnKold
@Captain Cold: Cóż, OP poprosił o zwięzłość, a nie ślad pamięci. Może zestaw mógłby zostać ponownie wykorzystany do innych operacji?
Guido Anselmi
1
Jasne, ale nawet tak: czy w całej uczciwości kiedykolwiek to zrobić samemu? Gdybym kiedykolwiek zobaczył to na wolności, uznałbym to za poważny WTF.
KaptajnKold
1
Tak, masz rację (dałem ci + 1-ki), ale zakłada się, że tego sprawdzenia nie przeprowadza się nigdzie indziej. Jeśli jest to wykonywane w kilku innych miejscach i / lub zmiany w teście, użycie zestawu może mieć sens. OP pozostawiam wybór najlepszego rozwiązania. Wszystko, co mówiło, że gdyby to było użytkowanie w pojedynkę, zgodziłbym się, że używanie zestawu zasługiwałoby na kapelusz wstydu As Clown.
Guido Anselmi
2
Właściwie uważam, że wybrana odpowiedź jest absolutnie okropna i gorsza niż użycie zestawu, ponieważ jest absolutnie nieczytelna.
Guido Anselmi
2

Jednym z moich ulubionych sposobów osiągnięcia tego jest biblioteka, taka jak 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
źródło
1
containsjest prawdopodobnie lepszym rozwiązaniem niżsome
Dennis
1
Nie ma potrzeby używania do tego biblioteki: someto funkcja prototypu Array w EC5.
KaptajnKold
2
To prawda, ale nie każdy ma dostępną obsługę EC5. Poza tym po prostu lubię podkreślenie. :)
jcreamer898
Jeśli jesteś już przy użyciu biblioteki jak podkreślenia, to chyba najprostszy sposób. W przeciwnym razie ładowanie całej biblioteki tylko dla jednej funkcji nie ma sensu.
Moshe Katz
2

w inny lub inny niesamowity sposób, który znalazłem, to ...

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

oczywiście, jak widać, idzie to o krok dalej i ułatwia ich stosowanie.

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

używając operatorów takich jak ~ && || ((), ()) ~~ jest w porządku tylko wtedy, gdy twój kod zepsuje się później. Nie będziesz wiedział, od czego zacząć. Czytelność jest więc DUŻA.

jeśli musisz, możesz go skrócić.

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

a jeśli chcesz zrobić odwrotność

('a' in oc(['a','b','c'])) || statement;
Muhammad Umer
źródło
2

Po prostu użyj switchoświadczenia zamiast ifoświadczenia:

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 działa również szybciej niż porównywanie wielu warunków warunkowych w ramach if

unmultimedio
źródło
2

W przypadku bardzo długich list ciągów ten pomysł pozwoliłby zaoszczędzić kilka znaków (nie mówiąc, że poleciłbym go w prawdziwym życiu, ale powinien działać).

Wybierz znak, o którym wiesz, że nie pojawi się w twoim teście. Typ, użyj go jako separatora, umieść je wszystkie w jednym długim ciągu i wyszukaj, że:

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

Jeśli twoje ciągi są bardziej ograniczone, możesz nawet pominąć ograniczniki ...

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

... ale w takim przypadku należy uważać na fałszywe alarmy (np. „embite” będzie pasować w tej wersji)

CupawnTae
źródło
2

Dla czytelności utwórz funkcję do testu (tak, funkcja jednowierszowa):

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

to nazwij to:


    if (isTypeDefined(test)) {

}
...
uff
źródło
1

Myślę, że przy pisaniu tego rodzaju warunku if są dwa cele.

  1. zwięzłość
  2. czytelność

W związku z tym czasami numer 1 może być najszybszy, ale później wezmę numer 2 dla łatwej konserwacji. W zależności od scenariusza często wybieram wariację odpowiedzi Waltera.

Na początek mam globalnie dostępną funkcję w ramach mojej istniejącej biblioteki.

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

a wtedy, gdy faktycznie chcę uruchomić warunek if podobny do twojego, utworzę obiekt z listą prawidłowych wartości:

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

Nie jest tak szybka jak instrukcja switch / case i trochę bardziej szczegółowa niż niektóre inne przykłady, ale często ponownie używam obiektu w innym miejscu w kodzie, co może być całkiem przydatne.

Podłączając jedną z próbek jsperf wykonanych powyżej, dodałem ten test i odmianę do porównania prędkości. http://jsperf.com/if-statements-test-techsin/6 Najbardziej interesującą rzeczą, jaką zauważyłem, jest to, że niektóre kombinacje testowe w Firefoksie są znacznie szybsze niż nawet Chrome.

scunliffe
źródło
1

Można to rozwiązać za pomocą prostej pętli for:

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

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

Używamy pierwszej sekcji pętli for do zainicjowania argumentów, które chcesz dopasować, drugiej sekcji do zatrzymania wykonywania pętli for, a trzeciej sekcji do spowodowania zakończenia pętli.


źródło