Як скоротити мої умовні твердження


154

У мене є дуже довгий умовний вислів, наприклад наступний:

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

Мені було цікаво, чи зможу я переробити цей вираз / вислів у більш стисну форму.

Будь-яка ідея, як цього досягти?


23
Ви можете помістити їх у масив та використовувати in?
jeremy


тепер лише якщо хтось міг перевірити, хто з них найшвидший
Мухаммед Умер

3
це може бути шоком для всіх, але те, що ОП - це явний переможець у швидкості !!!!!!! Можливо, змусити браузер оптимізувати для цього багато .. Результати: (1) якщо з ||. (2) switchзаяви. (3) регекс. (4) ~. jsperf.com/if-statements-test-techsin
Мухаммед Умер

3
Ви також можете підходити до цього неправильно. У цьому випадку ці 4 типи мають щось спільне. Що це? Якщо ми розглянемо це в більш крайньому випадку, що робити, якщо нам потрібно було додати ще 10 типів, щоб відповідати цій умові. Або 100? Якби їх було більше, ви, ймовірно, не могли б розглянути можливість використання цього рішення або будь-якого з інших запропонованих. Ви бачите велике, якщо таке твердження, і думаєте, що це кодовий запах, що є хорошим знаком. Ваш найкращий спосіб зробити це більш стислим, якби ви могли написати, якщо (test.your_common_condition). Це легше зрозуміти в цьому контексті і більш розширюється.
gmacdougall

Відповіді:


241

Введіть свої значення в масив і перевірте, чи є ваш елемент у масиві:

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

Якщо веб-переглядач, який ви підтримуєте, не має Array#includesметоду, ви можете використовувати цю поліфункцію .


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

Оновлення. Оскільки тепер у нас є 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. Див. Вище). Зауважте, що під inArrayчас обміну підписом методу PHP jQuery насправді імітує нативну 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"s" по всій.

Отже, все це було довготривалим способом сказати, що ~nповернеться лише 0тоді, якщо nє -1.


59
Незважаючи на те, що користувачі побитових операторів впевнені, що це сексуально !== -1? Хіба явна булева логіка не є більш доцільною, ніж неявне використання помилок нуля?
Філ

21
Приємно технічно, але мені це не подобається. На перший погляд не ясно, що робить код, що робить його нездійсненним. Я дуже віддаю перевагу відповіді "Юрія Галантера".
Джон Реа

65
-1 нові програмісти бачать відповіді так, думають, що це класний і прийнятний спосіб кодування, то через 5 років я повинен підтримувати їхній код і виривати волосся
BlueRaja - Danny Pflughoeft

23
Ця ідіома, безумовно, не поширена в таких мовах, як C #, Java або Python, які є моїми сферами досвіду. І я просто запитав тут декількох місцевих експертів Javascript, і ніхто з них ніколи не бачив цього робити раніше; тому явно це не так часто, як ви заявляєте. Його завжди слід уникати на користь набагато чіткішого та поширенішого != -1.
BlueRaja - Danny Pflughoeft

12
-1 через непотрібні бітфідлінг на мові, яка насправді не дуже багато вказує на представлення бітів. Плюс основна частина цієї відповіді пояснює бітфідлінг. Якщо вам доведеться написати 20 абзаців, щоб пояснити хак, чи дійсно це економить час?
пухнастий

242

Ви можете використовувати оператор переключення з падіння через:

switch (test.type) {

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

9
в основному такий же, як і якщо, метод індексу масиву набагато кращий
NimChimpsky

6
@kojiro це, на жаль, правильна відповідь: це, але неможливо привернути до цього увагу замість дивовижної хитрості.
Manu343726

1
Я знав, що ця відповідь повинна бути тут, але мені довелося прокрутити шлях донизу, щоб знайти її. Саме для цього було створено оператор перемикання і переноситься на багато інших мов. Я знайшов, що багато людей не знають про метод "провалитися" в операторі переключення.
Джиммі Джонсон

3
Це рішення є найшвидшим у Firefox та Safari та другим найшвидшим (після оригіналу ||) у Chrome. Дивіться jsperf.com/if-statements-test-techsin
pabouk

3
Я думаю, що це було б жахливо писати методом, якби у вас було багато умов ... Це якщо добре для кількох умов, але для більш ніж 10 я б пішов на масив, щоб зберегти код акуратним.
yu_ominae

63

Використовуючи Science: ви повинні робити те, що сказав 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це найшвидший метод?
користувач1477388

1
у firefox так, у хромі йогоif ( ...||...||...)...
Мухаммед Умер

8
Це швидше, якщо ви дійсно зробите багато циклів за цим входом, але це набагато повільніше, якщо у вас є одна петля з дуже великим n (кількість рядків "itemX"). Я зламав цей генератор коду, який ви можете використовувати для перевірки (або, можливо, спростування). obj["itemX"]надзвичайно швидко, якщо n великий. В основному, те, що швидко, залежить від контексту. Весело.
kojiro

3
Так це найшвидший метод, але чи це має значення ?
congusbongus

1
@Mich Не жертвуйте елегантністю коду лише заради швидкості. Це те, що скажуть тобі багато хто. Зрештою, просто використовуйте здоровий глузд.
Андре Фігейредо

32

Якщо ви порівнюєте рядки і є шаблон, подумайте про використання регулярних виразів.

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

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

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 намагається скоротити?
декарсон

1
ifЯкщо ви видалите весь пробіл, умовна заява @dcarson OP має 78 символів. Шахта займає 54 , якщо ви пишете так: test.type in {"itema":1,"itemb":1,"itemc":1,"itemd":1}. По суті, він використовує чотири символи на кожні два використання міни для кожної додаткової клавіші.
kojiro

1
але ви можете зробити: if (можливості [test.type]) і зберегти цілі 2 символи! :)
dc5

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

або якщо предмети не є однаковими, то:

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

9
"Деякі люди, стикаючись з проблемою, думають:" Я знаю, я буду використовувати регулярні вирази ". Зараз у них дві проблеми ". - Джеймі Завінський, 1997
Моше Кац

5
@MosheKatz Хоча я можу зрозуміти, що люди люблять бандажуватися щодо цієї цитати - і люди, звичайно, використовують регулярні вирази для абсолютно непридатних речей, але це не одна з них. У випадку, передбаченому ОП, це не лише відповідає критеріям, але й це дуже добре. Регулярні вирази не є по суті злими, і для цього створено відповідність рядків із чітко визначеними параметрами.
Thor84no

3
@ Thor84no Зазвичай я б припустив, що запитуючий насправді не намагається відповідати такому надуманому прикладу, як перший випадок, і що в реальному матчі не так просто, і в цьому випадку я не думаю, що RegEx йде бути правильним способом це зробити. Інакше кажучи, якщо ваш RegEx - це лише список опцій, розділених символами "труба", він не є більш читабельним, ніж будь-які інші пропозиції, і, можливо, значно менш ефективний.
Моше Кац

10

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

Це складно, якщо висловлювання, коли ви (або хтось інший) читаєте код через рік, ви будете переглядати, щоб знайти розділ, щоб зрозуміти, що відбувається. Заява з таким рівнем ділової логіки змусить вас спотикатися протягом декількох секунд, поки ви розробляєте те, що ви тестуєте. Якщо такий код, як цей, дозволить вам продовжити сканування.

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

@ Капітан Холодний: Ну ОП попросив стислість, а не слід пам’яті. Може бути, набір може бути використаний для інших операцій?
Гвідо Ансельмі

1
Зрозуміло, але навіть так: Ви б з усієї честі коли-небудь робили це самостійно? Якби я коли-небудь бачив це в дикій природі, я вважав би це основним WTF.
KaptajnKold

1
Так, ти маєш рацію (я дав тобі +1), але передбачається, що ця перевірка робиться ніде більше. Якщо це робиться в кількох інших місцях та / або змінюється тест, то використання набору може мати сенс. Я залишаю це на ОП, щоб вибрати найкраще рішення. Все, що говорило, якщо це було одиночне використання, я погодився б, що використання набору заслужило б шапку сорому як клоун.
Гвідо Ансельмі

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


1
containsМожливо, краще рішення, ніжsome
Денніс

1
Для цього не потрібно використовувати бібліотеку: someце функція в прототипі масиву в 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


2

Для дуже довгих списків рядків ця ідея врятувала б декілька персонажів (не кажучи, що я рекомендую її в реальному житті, але вона має працювати).

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

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

Якщо ваші струни будуть додатково обмежені, ви можете навіть опустити роздільники ...

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

... але вам доведеться бути обережними з помилковими позитивами в такому випадку (наприклад, "embite" відповідатиме у цій версії)


2

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

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

тоді зателефонуйте:


    if (isTypeDefined(test)) {

}
...

1

Я думаю, що є 2 цілі, коли пишеться така умова if.

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

Це не так швидко, як оператор перемикання / випадку і трохи більш багатослівний, ніж деякі інші приклади, але я часто отримую повторне використання об'єкта в іншому місці коду, що може бути дуже зручним.

Piggybacking на одному з зразків jsperf, зробленому вище, я додав цей тест і варіацію для порівняння швидкостей. http://jsperf.com/if-statements-test-techsin/6 Найцікавіше, що я зазначив, це те, що деякі тестові комбінації у Firefox набагато швидші, ніж навіть у Chrome.


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 для запуску, а третій розділ, щоб змусити цикл з часом вийти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.