Как сократить мои условные высказывания

154

У меня есть очень длинное условное утверждение, подобное следующему:

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

Мне было интересно, смогу ли я переформулировать это выражение / утверждение в более сжатую форму.

Есть идеи, как этого добиться?

FlyingCat
источник
23
Вы можете поместить их в массив и использовать in?
Джереми
2
snook.ca/archives/javascript/testing_for_a_v
Мухаммед Умер
теперь только если кто-то может проверить, какой из них самый быстрый
Мухаммед Умер
3
это может быть шоком для всех, но у ОП есть явный победитель в скорости !!!!!!! Возможно, браузер оптимизирует для этого много .. Результаты: (1) если с ||. (2) switchзаявления. (3) регулярное выражение. (4) ~. jsperf.com/if-statements-test-techsin
Мухаммед Умер
3
Вы также можете подходить к этому неправильно. В этом случае эти 4 типа имеют что-то общее. Что это? Если мы примем это к более экстремальному случаю, что если нам нужно добавить еще 10 типов, чтобы соответствовать этому условию. Или 100? Если бы их было больше, вы, вероятно, не рассматривали бы использование этого решения или любого другого, предложенного. Вы видите такой большой оператор if и думаете, что это запах кода, что является хорошим знаком. Ваш лучший способ сделать это более сжатым, если бы вы могли написать if (test.your_common_condition). Это легче понять в этом контексте и более расширяемо.
gmacdougall

Ответы:

241

Поместите ваши значения в массив и проверьте, находится ли ваш элемент в массиве:

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

Если браузер, который вы поддерживаете, не имеет Array#includesметода, вы можете использовать этот polyfill .


Краткое объяснение ~ярлыка тильды:

Обновить: как у нас теперь есть includesметод, нет смысла использовать ~взломать больше. Просто оставьте это здесь для людей, которые заинтересованы в знании того, как это работает и / или столкнулись с этим в чужом коде.

Вместо проверки, если результат indexOf есть >= 0, есть хороший маленький ярлык:

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

Вот скрипка: http://jsfiddle.net/HYJvK/

Как это работает? Если элемент найден в массиве, indexOfвозвращает его индекс. Если предмет не был найден, он вернется -1. Не вдаваясь в подробности, ~это побитовый оператор НЕ , который вернет0 только для -1.

Мне нравится использовать ~ярлык, поскольку он более лаконичен, чем сравнение возвращаемого значения. Хотелось бы, чтобы в JavaScript была in_arrayфункция, которая возвращала бы логическое значение напрямую (аналогично PHP), но это просто желаемое за действительное ( Обновление: теперь это происходит. Это называется includes. Смотрите выше) Обратите внимание, что jQuery inArray, разделяя сигнатуру метода PHP, фактически имитирует нативную indexOfфункциональность (которая полезна в разных случаях, если индекс - это то, что вам действительно нужно).

Важное примечание: использование ярлыка тильды, похоже, обернулось спорами, как некоторые яростно полагают, что код недостаточно ясен и его следует избегать любой ценой (см. Комментарии к этому ответу). Если вы разделяете их мнение, вы должны придерживаться .indexOf(...) >= 0решения.


Немного более длинное объяснение:

Целые числа в JavaScript подписаны, что означает, что самый левый бит зарезервирован как бит знака; флаг, чтобы указать, является ли число положительным или отрицательным, с1 отрицательным.

Вот некоторые примеры положительных чисел в 32-битном двоичном формате:

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

Теперь вот те самые цифры, но отрицательные:

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

Почему такие странные комбинации для отрицательных чисел? Просто. Отрицательное число - это просто инверсия положительного числа + 1; добавление отрицательного числа к положительному числу должно всегда давать0 .

Чтобы понять это, давайте сделаем простую двоичную арифметику.

Вот как мы могли бы добавить -1к +1:

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

А вот как мы могли бы добавить -15к +15:

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

Как мы получаем эти результаты? Выполняя регулярное сложение, как нас учили в школе: вы начинаете с самого правого столбца и складываете все строки. Если сумма больше наибольшего однозначного числа (которое в десятичном виде 9, но в двоичном1 ), мы переносим остаток в следующий столбец.

Теперь, как вы заметите, при добавлении отрицательного числа к его положительному номеру, самый правый столбец, который не является всем 0s, всегда будет иметь два 1s, которые при сложении вместе приведут к 2. Двоичное представление двух существ 10, мы переносим в 1следующий столбец и помещаем 0результат в первый столбец. Все остальные столбцы слева имеют только одну строку с символом «a» 1, поэтому перенос 1из предыдущего столбца будет снова складываться 2, что затем будет перенесено ... Этот процесс повторяется до тех пор, пока мы не доберемся до самого левого столбца, где « 1Переносить» некуда, поэтому он переполняется и теряется, и мы остаемся со 0всеми.

Эта система называется дополнением 2 . Вы можете прочитать больше об этом здесь:

Представление дополнения 2 для подписанных целых чисел .


Теперь, когда ускоренный курс в дополнении 2 закончен, вы заметите, что -1это единственное число, двоичное представление которого1 повсюду.

Используя ~побитовый оператор NOT, все биты в данном номере инвертируются. Единственный способ 0вернуться от инвертирования всех битов, если мы начали с1 все сначала.

Итак, все это было многословным способом сказать, что ~nвернется, только 0если nесть -1.

Джозеф Силбер
источник
59
Хотя использование побитовых операторов действительно сексуально, действительно ли это лучше, чем !== -1каким-либо мыслимым способом? Разве явная булева логика не является более подходящей, чем неявное использование фальши нуля?
Фил
21
Очень красиво, но мне это не нравится. С первого взгляда неясно, что делает код, что делает его неуправляемым. Я предпочитаю ответ "Юрия Галантера".
Джон Ри
65
-1 новые программисты видят такие ответы, считают, что это крутой и приемлемый способ кодирования, и через 5 лет я должен сохранить свой код и вырвать свои волосы
BlueRaja - Дэнни Пфлугхёфт
23
Эта идиома определенно не распространена в таких языках, как C #, Java или Python, которые являются моей областью знаний. И я только что спросил нескольких местных экспертов по Javascript здесь, и никто из них никогда не видел, чтобы это было сделано раньше; так что это явно не так часто, как вы утверждаете. Следует всегда избегать в пользу гораздо более понятного и более распространенного != -1.
BlueRaja - Дэнни Пфлугхофт
12
-1 из-за ненужного искажения битов в языке, который на самом деле не особо определяет битовые представления. Кроме того, основная часть этого ответа объясняет бесполезность. Если вам нужно написать 20 абзацев, чтобы объяснить взлом, действительно ли это экономит время?
пушистый
242

Вы можете использовать оператор switch с fall thru:

switch (test.type) {

  case "itema":
  case "itemb":
  case "itemc":
  case "itemd":
    // do something
}
Юрий Галантер
источник
9
По сути, он такой же, как если бы, метод индекса массива намного лучше
NimChimpsky
6
@kojiro, к сожалению, правильный ответ таков, но невозможно привлечь внимание к этому вместо удивительного трюка с битовой матрицей.
Manu343726
1
Я знал, что этот ответ должен быть здесь, но мне пришлось прокрутить путь вниз, чтобы найти его. Это именно то, для чего был разработан оператор switch и он перенесен на многие другие языки. Я обнаружил, что многие люди не знают о методе «провала» в операторе switch.
Джимми Джонсон
3
Это самое быстрое решение для Firefox и Safari и второе по быстродействию (после оригинала ||) в Chrome. См. Jsperf.com/if-statements-test-techsin
Пабук
3
Я думаю, что было бы ужасно писать в методе, если бы у вас было много условий ... Это хорошо для нескольких условий, но для более чем 10 я бы пошел в массив, чтобы сохранить код в чистоте.
yu_ominae
63

Используя науку: вы должны делать то, что сказал idfah, и это для максимальной скорости, в то время как код должен быть коротким:

Это быстрее, чем ~метод

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

http://jsperf.com/if-statements-test-techsin введите описание изображения здесь (Верхний набор: Chrome, нижний набор: Firefox)

Вывод :

Если возможности мало , и вы знаете , что некоторые из них являются более вероятны , чем вы получаете максимальную производительность из if ||, switch fall throughиif(obj[keyval]) .

Если возможностей много , и любой из них может быть самым распространенным, другими словами, вы не можете знать, какой из них наиболее вероятен, чем вы получаете наибольшую производительность из поиска объектов if(obj[keyval])иregex соответствует ли он.

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

Я буду обновлять, если что-то новое появится.

Мухаммед Умер
источник
2
+1 за действительно хороший пост! Если я правильно понимаю, switch caseсамый быстрый способ?
user1477388
1
в Firefox да, в Chrome егоif ( ...||...||...)...
Мухаммед Умер
8
Это быстрее, если вы действительно делаете много циклов над этим вводом, но гораздо медленнее, если у вас есть один цикл с очень большим n (числом строк "itemX"). Я взломал этот генератор кода, который вы можете использовать для проверки (или, возможно, опровержения). obj["itemX"]очень быстро, если n большое. В основном то, что быстро, зависит от контекста. Радоваться, веселиться.
Кодзиро
3
Так что это самый быстрый метод, но имеет ли это значение ?
congusbongus
1
@Mich Не жертвуйте элегантностью кода ради скорости. Это то, что многие многие скажут вам. В конце концов, просто используйте здравый смысл.
Андре Фигейредо
32

Если вы сравниваете со строками и существует шаблон, рассмотрите возможность использования регулярных выражений.

В противном случае, я подозреваю, что попытка сократить его просто запутает ваш код. Попробуйте просто обернуть строки, чтобы сделать его красивым.

if (test.type == 'itema' ||
    test.type == 'itemb' ||
    test.type == 'itemc' ||
    test.type == 'itemd') {
    do something.
}
idfah
источник
4
этот ответ является победителем с точки зрения скорости jsperf.com/if-statements-test-techsin
Мухаммед Умер
1
Это также легче всего расширить, когда проект переходит в режим обслуживания (с такими правилами, как (test.type == 'itemf' && foo.mode == 'detailed'))
Izkata
16
var possibilities = {
  "itema": 1,
  "itemb": 1,
  "itemc": 1,
…};
if (test.type in possibilities) {  }

Использование объекта в качестве ассоциативного массива - довольно распространенная вещь, но, поскольку у JavaScript нет собственного набора, вы также можете использовать объекты в качестве дешевых наборов.

Кодзиро
источник
Как это короче, чем обычно, если заявление о том, что FlyingCat пытается сократить?
dcarson
1
Условное ifвыражение оператора @dcarson принимает 78 символов, если вы удалите все пробелы. Шахта занимает 54 , если вы пишете так: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. По сути, он использует четыре символа для каждых двух моих использования для каждого дополнительного ключа.
Кодзиро
1
но вы можете сделать: если (возможности [test.type]) и сохранить целых 2 символа! :)
DC5
15
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }

или если предметы не одинаковы, то:

if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Matt
источник
9
«Некоторые люди, сталкиваясь с проблемой, думают:« Я знаю, я буду использовать регулярные выражения ». Теперь у них две проблемы ". - Джейми Завински, 1997
Моше Кац
5
@MosheKatz Хотя я могу понять, что людям нравится кричать об этой цитате - и люди, безусловно, используют регулярные выражения для совершенно неподходящих вещей, но это не один из них. В случае, предусмотренном ФП, это не только соответствует критериям, но и очень хорошо. Регулярные выражения по своей природе не являются злом, и для этого предназначаются строки с четко определенными параметрами.
Thor84no
3
@ Thor84no Обычно, я предполагаю, что спрашивающий фактически не пытается сопоставить с таким надуманным примером, как первый случай, и что реальные совпадения не так просты, и в этом случае я не думаю, что RegEx собирается быть правильным способом сделать это. Иными словами, если ваш RegEx - это просто список параметров, разделенных символами и пробелами, он не более читабелен, чем любое из других предложений, и, возможно, значительно менее эффективен.
Моше Кац
10

Отличные ответы, но вы можете сделать код гораздо более читабельным, поместив один из них в функцию.

Это сложный оператор if, когда вы (или кто-то еще) прочитали код в течение нескольких лет, вы будете просматривать этот раздел, чтобы понять, что происходит. Заявление с таким уровнем бизнес-логики заставит вас запнуться на несколько секунд, пока вы решаете, что тестируете. Где, как этот код, позволит вам продолжить сканирование.

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

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

Назовите вашу функцию в явном виде, чтобы сразу было понятно, что вы тестируете, и ваш код будет намного легче сканировать и понимать.

Фрэн Хой
источник
1
Лучший ответ, который я видел здесь. Действительно, я вижу, что людям нет дела до принципов хорошего дизайна. Просто хочу быстро что-то исправить и забыть улучшить код, чтобы система могла обслуживаться в будущем!
Майконн
Как насчет просто комментировать как // CheckIfBusinessRuleIsTrue?
daniel1426
4

Вы можете поместить все ответы в набор Javascript, а затем просто вызвать .contains()его.

Вы все равно должны объявить все содержимое, но встроенный вызов будет короче.

Что-то вроде:

var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
Гвидо Ансельми
источник
4
Это кажется невероятно расточительным способом выполнить то, что пытается сделать ОП. Поэтому, хотя вы можете включить дополнительную стороннюю библиотеку, создать экземпляр объекта и вызвать метод для него, вы, вероятно, не должны этого делать.
KaptajnKold
@Captain Cold: Ну, ОП попросил краткости, а не памяти. Может быть, набор можно использовать для других операций?
Гвидо Ансельми
1
Конечно, но даже так: вы бы во всей честности когда - либо сделать это самостоятельно? Если бы я когда-либо видел это в дикой природе, я бы счел это главным WTF.
KaptajnKold
1
Да, вы правы (я дал вам +1), но предполагается, что эта проверка не выполняется больше нигде. Если это делается в нескольких других местах и ​​/ или тестовых изменениях, то использование набора может иметь смысл. Я оставляю это на OP, чтобы выбрать лучшее решение. Все это говорит о том, что если бы это было одиночное использование, я бы согласился с тем, что использование этого набора заслуживает позора как клоуна.
Гвидо Ансельми
2
Я действительно думаю, что выбранный ответ абсолютно ужасен и хуже, чем использование набора, поскольку он абсолютно нечитабелен.
Гвидо Ансельми
2

Один из моих любимых способов сделать это с помощью библиотеки, например 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
источник
1
containsвозможно лучшее решение, чемsome
Деннис
1
Для этого не нужно использовать библиотеку: someэто функция прототипа Array в EC5.
KaptajnKold
2
Правда, но не у всех есть поддержка EC5. Плюс, я просто очень люблю подчеркивание. :)
jcreamer898
Если вы уже используете библиотеку типа подчеркивания, это, вероятно, самый простой способ. В противном случае не имеет смысла загружать целую библиотеку только для одной функции.
Моше Кац
2

другой путь или другой удивительный способ, который я нашел, это ...

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

конечно, как вы можете видеть, это делает вещи на шаг впереди и облегчает их следование логике.

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

используя операторы, такие как ~ && || ((), ()) ~~ хорошо, только если ваш код сломается позже. Вы не будете знать, с чего начать. Так что читабельность БОЛЬШАЯ.

если вам нужно, вы можете сделать его короче.

('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' in oc(['a','b','c'])) || statement;
Мухаммед Умер
источник
2

Просто используйте switchутверждение вместо ifутверждения:

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 также работает быстрее, чем сравнение множества условий в if

unmultimedio
источник
2

Для очень длинных списков строк эта идея сохранит несколько символов (не говоря уже о том, что я бы порекомендовал его в реальной жизни, но он должен работать).

Выберите символ, который, как вы знаете, не встречается в вашем test.type, используйте его в качестве разделителя, вставьте их в одну длинную строку и найдите:

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

Если ваши строки окажутся более жесткими, вы можете даже опустить разделители ...

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

... но вы должны быть осторожны с ложными срабатываниями в этом случае (например, "embite" будет соответствовать в этой версии)

CupawnTae
источник
2

Для удобства чтения создайте функцию для теста (да, функцию из одной строки):

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

тогда назовите это:


    if (isTypeDefined(test)) {

}
...
zaph
источник
1

Я думаю, что есть 2 цели при написании этого условия.

  1. краткость
  2. читабельность

Таким образом, иногда # 1 может быть самым быстрым, но я возьму № 2 для удобства обслуживания позже. В зависимости от сценария я часто выбираю вариант ответа Уолтера.

Для начала у меня есть глобально доступная функция как часть моей существующей библиотеки.

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

и затем, когда я действительно хочу выполнить условие if, подобное вашему, я бы создал объект со списком допустимых значений:

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

Это не так быстро, как оператор switch / case, и немного более многословно, чем некоторые другие примеры, но я часто получаю повторное использование объекта в другом месте кода, что может быть очень удобно.

В сочетании с одним из образцов jsperf, сделанных выше, я добавил этот тест и вариацию для сравнения скоростей. http://jsperf.com/if-statements-test-techsin/6 Самое интересное, что я заметил, это то, что некоторые тестовые комбинации в Firefox выполняются намного быстрее, чем даже Chrome.

scunliffe
источник
1

Это можно решить с помощью простого цикла:

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

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

Мы используем первый раздел цикла for, чтобы инициализировать аргументы, которые вы хотите сопоставить, второй раздел, чтобы остановить цикл for, и третий раздел, чтобы в конечном итоге завершить цикл.


источник