Недавно (или не совсем недавно, в зависимости от того, когда вы читаете эту статью), у меня была дискуссия с некоторыми товарищами по команде о том, как справляться с условиями, требующими многократной оценки, обычно в таких случаях люди любят использовать оператор switch или огромный if с несколько else if. В этой статье я собираюсь описать третий способ (подход, который я предпочитаю), мы будем использовать объекты для быстрого поиска соответствию условию.
Оператор switch позволяет нам вычислять выражение и делать что-то особенное в зависимости от значения переданного выражения, обычно, когда вы учитесь писать код и алгоритмы, вы узнаете, что вы можете использовать его специально для нескольких вычислений, вы начинаете его использовать, и все выглядит хорошо, и вы быстро понимаете, что это дает вам большую свободу, да !, но будьте осторожны, большая свобода приходит с большой ответственностью.
Давайте быстро посмотрим, как выглядит типичный оператор switch:
switch (expression) { case x: { /* Your code here */ break; } case y: { /* Your code here */ break; } default: { /* Your code here */ } }
Отлично, теперь есть пара вещей, на которые вы, возможно, не обратили внимание:
Ключевое слово break позволяет нам остановить выполнение блоков, когда условие уже выполнено. Если не добавлять ключевое слово break в оператор switch, не будет ни какой ошибки. Но если ключевое слово break пропущено случайно, это может означать, что выполнится код, о котором вы даже не подозреваете, что также добавит несоответствия нашим реализациям, мутациям, утечкам памяти и уровням сложности при отладке проблемы. Давайте посмотрим на представление этой проблемы:
switch ('first') { case 'first': { console.log('first case'); } case 'second': { console.log('second case'); } case 'third': { console.log('third case'); break; } default: { console.log('infinite'); } }
Если выполнить этот фрагмент кода в своей консоли, вы увидите, что типа такого:
firt case second case third case
Оператор switch выполнит блок во втором и третьем условие, даже если первое условие уже было правильным, затем он находит ключевое слово break в третьем условие и остановит выполнение, без предупреждений или ошибок в консоли.
Фигурные скобки представляют блоки кода в javascript, так как начиная с ECMAscript 2015 мы можем объявлять переменные с блочной областью с использованием операторов, таких как const или let, что отлично (но не так хорошо для switch), так как фигурные скобки не обязательны, мы можем получить ошибки, из-за дублирования переменных, давайте посмотрим, что произойдет, когда мы выполним код ниже:
switch ('second') { case 'first': let position = 'first'; console.log(position); break; case 'second': let position = 'second'; console.log(position); break; default: console.log('infinite'); }
мы получим:
Uncaught SyntaxError: Identifier 'position' has already been declared
Возникает ошибка, потому что переменная position уже была объявлена в первом условие, и поскольку там нет фигурных скобок, она поднимается, а затем, когда во втором условие пытаемся объявить ее же, то получается что она уже существует и BOOM.
А теперь представьте, что может произойти при использовании операторов switch без ключевого слова break и фигурными скобками:
switch ('first') { case 'first': let position = 'first'; console.log(position); case 'second': console.log(`second has access to ${position}`); position = 'second'; console.log(position); default: console.log('infinite'); }
В консоли будет следующее:
first second has access to first second infinite
Только представьте, количество ошибок и мутаций, которые могут быть внесены из-за этого… возможности безграничны … Во всяком случае мы пришли сюда, чтобы поговорить о другом подходе, мы пришли сюда, чтобы поговорить об объектах.
Поиск объектов выполняется быстро и быстрее по мере увеличения их размера, а также позволяет нам представлять данные в виде пар ключ-значение, что является превосходным условным выполнением.
давайте начнем с чего-то простого, такого как примеры со switch, давайте предположим, что нам нужно сохранить и вернуть строку условно, используя объект, которые мы заранее подготовим:
const getPosition = position => { const positions = { first: 'first', second: 'second', third: 'third', default: 'infinite' }; return positions[position] || positions.default; }; const position = getPosition('first'); // Returns 'first' const otherValue = getPosition('fourth'); // Returns 'infinite'
Если вы хотите еще больше сократить количество кода, мы могли бы использовать функции со стрелками:
const getPosition = position => ({ first: 'first', second: 'second', third: 'third' }[position] || 'infinite'); const positionValue = getPosition('first'); // Returns 'first' const otherValue = getPosition('fourth'); // Returns 'infinite'
Это делает то же самое, что и предыдущая реализация, но мы использовали более компактное решение с меньшим количеством строк кода.
Давайте теперь будем немного более реалистичны, не все условия, которые мы напишем, будут возвращать простые строки, многие из них будут возвращать логические значения, выполнять функции и многое другое.
Мне нравится создавать свои функции таким образом, чтобы они возвращали согласованные типы значений, но, поскольку javascript — это язык с динамической типизацией, могут быть случаи, когда функция может возвращать динамические типы, поэтому я приму это во внимание в этом примере и сделаю функцию, которая возвращает boolean, undefined или string, если ключ не найден.
const isNotOpenSource = language => ({ vscode: false, sublimetext: true, neovim: false, fakeEditor: undefined }[language] || 'unknown'); const sublimeState = isNotOpenSource('sublimetext'); // Returns true
Выглядит отлично, правда ?, но подождите, похоже, у нас есть проблема … что произойдет, если мы вместо этого вызовем функцию с аргументом ‘vscode‘ или fakeEditor ?, ммм, давайте посмотрим:
У нас будет такая же проблема для ключа fakeEditor.
О нет, хорошо, не паникуйте, давайте разберемся с этим:
const isNotOpenSource = editor => { const editors = { vscode: false, sublimetext: true, neovim: false, fakeEditor: undefined, default: 'unknown' }; return editor in editors ? editors[editor] : editors.default; }; const codeState = isNotOpenSource('vscode'); // Returns false const fakeEditorState = isNotOpenSource('fakeEditor'); // Returns undefined const sublimeState = isNotOpenSource('sublimetext'); // Returns true const webstormState = isNotOpenSource('webstorm'); // Returns 'unknown'
И это решает проблему, но … я хочу, чтобы вы спросили себя одну вещь: действительно ли это проблема здесь? Я думаю, что мы должны больше беспокоиться о том, зачем нам нужна функция, которая в первую очередь возвращает boolean, undefined или string, это серьезное несоответствие, в любом случае, это просто возможное решение для очень частного случая.
Давайте продолжим с функциями, часто мы находимся в положении, когда нам нужно выполнить функцию в зависимости от аргументов, давайте предположим, что нам нужно проанализировать некоторые входные значения в зависимости от типа ввода, но если указанный тип анализатора не будет зарегистрирован, то мы просто вернем значение:
const getParsedInputValue = type => { const emailParser = email => `email, ${email}`; const passwordParser = password => `password, ${password}`; const birthdateParser = date => `date , ${date}`; const parsers = { email: emailParser, password: passwordParser, birthdate: birthdateParser, default: value => value }; return parsers[type] || parsers.default; }; // We select the parser with the type and then passed the dynamic value to parse const parsedEmail = getParsedInputValue('email')('myemail@gmail.com'); // Returns email, myemail@gmail.com const parsedName = getParsedInputValue('name')('Enmanuel'); // Returns 'Enmanuel'
Если бы у нас была похожая функция, которая на этот раз возвращает другие функции, но без параметров, мы могли бы улучшить код для непосредственного возврата при вызове первой функции, что-то такого:
const getValue = type => { const email = () => 'myemail@gmail.com'; const password = () => '12345'; const parsers = { email, password, default: () => 'default' }; return (parsers[type] || parsers.default)(); // we immediately invoke the function here }; const emailValue = getValue('email'); // Returns myemail@gmail.com const passwordValue = getValue('name'); // Returns default
Операторы switch позволяют нам определять общие блоки кода для нескольких условий.
switch (editor) { case 'atom': case 'sublime': case 'vscode': return 'It is a code editor'; break; case 'webstorm': case 'pycharm': return 'It is an IDE'; break; default: return 'unknown'; }
Как мы сделаем то же самое, используя объекты ?, мы могли бы сделать это следующим образом:
const getEditorType = type => { const itsCodeEditor = () => 'It is a code editor'; const itsIDE = () => 'It is an IDE'; const editors = { atom: itsCodeEditor, sublime: itsCodeEditor, vscode: itsCodeEditor, webstorm: itsIDE, pycharm: itsIDE, default: () => 'unknown' }; return (editors[type] || editors.default)(); }; const vscodeType = getEditorType('vscode'); // Returns 'It is a code editor'
И теперь у нас есть подход, который:
Как и ожидалось, все подходы имеют свои недостатки, и этот не является исключением из правила.
Эта статья не предназначена для того, чтобы изменить ваш стиль кодирования или заставить вас перестать использовать операторы switch, она просто пытается повысить осведомленность, чтобы ее можно было правильно использовать, а также открыла ваш разум для поиска новых альтернатив. Я просто поделился подходом, который мне нравится использовать, но есть и другие, например, вы можете взглянуть на вариант ES6 под названием сопоставление с образцом (pattern matching), если вам он не понравится, вы можете продолжить исследование.
ОК, разработчики будущего, вот и все, надеюсь, вам понравилась статья, если так то возможно вам понравиться и эта статья о фабричном паттерне (article about factory pattern). Кроме того, не забудьте поделиться этим и подписаться, вы можете найти меня в твиттере или связаться со мной по электронной почте duranenmanuel@gmail.com, до встречи в следующем.
Оригинальная статья: Enmanuel Durán — Why I prefer objects over switch statements
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…
View Comments
Оверинжиниринг на пустом месте. Используйте лопату, чтобы копать и молоток, чтобы забивать. Не наоборот! Оператор switch сразу говорит, что сейчас будет перебор вариантов и если он делает что-то не то, то круг ошибок ограничен. С объектами надо ещё понять, что это и зачем это и только потом найти ошибку. Если уж так хочется, то хотя бы коллекцию в отдельную константу вынести с ВОТ_ТАКИМ_НЕЙМИНГОМ или Map заюзать.
Да и вообще пишите семантический код! Об этом в один голос твердят все разработчики браузерных движков.
👍
🤝
Свич-кейс ужасен в плане читаемости и вохможностей. Редко когда он действительно нужен, в большитсве случаев он сильно избыточен. Нам крупно повезло что сегодня мы можем писать более элегантные решения с помощью хэш таблиц (хотя последний пример автора не лучший пример того что можно сделать, понимаю почему у новичков глаза вытекают).