Працюючи з JavaScript ми часто вимушені мати справу з умовними виразами. У статті оглянемо п'ять порад, які дозволять писати більш чисті умовні переходи.
1. Використовуйте Array.includes
для декількох критеріїв
// умова
function test(fruit) {
if (fruit == 'apple' || fruit == 'strawberry') {
console.log('red');
}
}
На перший погляд, код вище не містить ніяких проблем. Але якщо ми додамо більше червоних фруктів (наприклад, cherry
та cranberries
)? Ви запропонуєте додати декілька умов з оператором ||
?
Краще скористатися Array.includes
.
function test(fruit) {
// виносимо умови в масив
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (redFruits.includes(fruit)) {
console.log('red');
}
}
З таким підходом ми виносимо red fruits
(умову) в окремий масив. Так код виглядає чистішим.
2. Застосовуйте return
, щоб уникнути надлишкових вкладень
Додамо дві умови до попереднього прикладу:
- якщо не зустрічається фрукт, викидати помилку;
- приймати та виводити назву плодів, кількість яких перевищує 10.
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// умова 1: Фрукт наявний
if (fruit) {
// умова 2: Повинен бути червоним
if (redFruits.includes(fruit)) {
console.log('red');
// умова 3: Кількість
if (quantity > 10) {
console.log('big quantity');
}
}
} else {
throw new Error('No fruit!');
}
}
// Результати
test(null); // Помилка: немає фруктів
test('apple'); // Виведе: red
test('apple', 20); // Виведе: red, big quantity
Погляньмо на код. Ми маємо:
- один
if/else
вираз, що фільтрує помилку; - три рівні вкладення
if
(умова 1, 2 та 3).
Загальне правило, яким слід керуватись:використовувати return
, перш ніж знаходимо невідповідність умові.
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// умова 1: спершу виводимо помилку
if (!fruit) throw new Error('No fruit!');
// умова 2: фрукти повинні бути червоними
if (redFruits.includes(fruit)) {
console.log('red');
// умова 3: їх повинно бути багато
if (quantity > 10) {
console.log('big quantity');
}
}
}
Так ми прибираємо зайвий рівень вкладеності. Такий формат особливо зручний, якщо тіло if-виразу громіздке (уявіть, що вам необхідно скролити до самого кінця, щоб дізнатися чи є else
).
З інверсією ми ще більше зменшимо вкладення. Погляньте на умову 2, щоб зрозуміти як це працює:
/_ використовуємо return, перш ніж знаходимо невідповідність умові
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (!fruit) throw new Error('No fruit!'); // умова 1: спершу викидуємо помилку
if (!redFruits.includes(fruit)) return; // умова 2: зупинимось, якщо фрукт не червоний
console.log('red');
// умова 3: фрукт повинен бути у великій кількості
if (quantity > 10) {
console.log('big quantity');
}
}
Ми змінили другу умову на протилежну — і наш код позбувся вкладень. Такий підхід буде корисним, коли ми одразу хочемо зупинити подальше виконання коду, якщо умова не виконується.
Однак, це не жорстке правило. Запитайте себе чи буде така версія (без вкладень) кращою/ читабельнішою, ніж попередня версія (умова 2 з вкладенням)?
Особливості умови 2:
- з вкладеними if код коротший та більш прямолінійний;
- інверсія ускладнює розуміння логіки.
Завжди прагніть зменшити кількість вкладень з return
. Але не перестарайтеся.
Якщо цікаво, існує багато публікацій та обговорень на StackOverflow з цієї теми:
3. Використовуйте параметри функцій за замовчуванням та деструктуризацію
Ви, напевне, вже зустрічали такий код. При роботі з JavaScript необхідно перевіряти змінні на null
/undefined
та присвоювати значення за замовчуванням:
function test(fruit, quantity) {
if (!fruit) return;
const q = quantity || 1; // Якщо кількість не передбачена, встановлюємо 1 за замовчуванням
console.log(`We have ${q} ${fruit}!`);
}
/Результати
test('banana'); // У нас 1 банан!
test('apple', 2); // У нас 2 яблука!
З параметрами функцій за замовчуванням можна уникнути змінної q
.
function test(fruit, quantity = 1) { // якщо кількість не передбачено, встановлюємо 1 за замовчуванням
if (!fruit) return;
console.log(`We have ${quantity} ${fruit}!`);
}
//Результати
test('banana'); // У нас 1 банан!
test('apple', 2); // У нас 2 яблука!
Такий код набагато простіший у розумінні, чи не так?
Кожен параметр може мати особливе значення за замовчуванням. Наприклад, ми можемо присвоїти значення за замовчуванням і для fruit
також : function test(fruit = 'unknown', quantity = 1)
.
Якщо ж fruit
є об'єктом? Чи буде у нього значення за замовчуванням?
function test(fruit) {
// виводить назву фрукта за наявністю
if (fruit && fruit.name) {
console.log (fruit.name);
} else {
console.log('unknown');
}
}
//результати
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // яблуко
Погляньте на цей приклад. Ми хочемо вивести назву фрукта, якщо це можливо, або ж результатом буде unknown
. Можна уникнути умову fruit && fruit.name
. Тут на допомогу приходять параметри функції за замовчуванням та деструктуризація.
// деструктуризація — процес, коли ми отримуємо лише властивість name
// присвоюємо пустий об'єкт за замовчуванням
function test({name} = {}) {
console.log (name || 'unknown');
}
//Результати
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // яблуко
Оскільки нам потрібна лише властивість name
об'єкта fruit
, ми можемо деструктуризувати цей параметр так: {name}
. Потім можемо використати name
як змінну у нашому коді замість виразу fruit.name
.
Потім за замовчуванням присвоюємо пустий об'єкт {}
. Якщо цього не зробимо, то отримаємо помилку при виконанні: test(undefined) - Неможливо деструктуризувати властивість name у 'undefined' чи null'
, тому що в undefined
не існує властивості name
.
Якщо ви не проти сторонніх бібліотек, існують способи скоротити перевірку на null
:
- використовуйте
get
функцію з Lodash; - використовуйте бібліотеку з відкритим сирцевим кодом idx від Facebook (з Babel.js).
Нижче приклад використання Lodash:
function test(fruit) {
console.log(__.get(fruit, 'name', 'unknown'); // отримуємо властивість name. Якщо недоступна, присвоюємо значення 'unknown'
}
//Результат
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // яблуко
Ви можете запустити демо-код тут. Крім того, якщо ви шанувальник функціонального програмування, можете спробувати Lodash FP — функціональну версію Lodash (назву методу замінено на get
або get0r
).
4. Віддавайте перевагу Map/Літералу об'єкта перед Switch-виразами
Погляньмо на приклад нижче. Тут ми виводимо фрукти відповідно до кольору.
function test(color) {
// використовується switch для пошуку фруктів за кольором
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
//Результати
test(null); // []
test('yellow'); // ['banana', 'pineapple']
На перший погляд з кодом усе гаразд, але він занадто громіздкий. Ми можемо переписати усе чистіше з літералом об'єкта.
// використовуємо літерал об'єкта для пошуку фруктів за кольором
const fruitColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum']
};
function test(color) {
return fruitColor[color] || [];
}
Як альтернативу спробуйте Map
:
// використовуємо Map для знаходження фруктів за кольором
const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);
function test(color) {
return fruitColor.get(color) || [];
}
Map — доступний у JavaScript з ES2015. Дозволяє зберігати пари «ключ/значення».
Чи варто уникати switch
? Не обмежуйте себе таким чином. Бажано використовувати літерали об'єктів усюди, де це можливо. Але це не жорстке правило. Використовуйте те, що буде більш прийнятним для вашого випадку.
Якщо ви хочете визначити для себе — switch
vs object literal
, огляньте цю статтю.
Рефакторинг
Ми можемо зробити рефакторинг нашого коду з Array.filter
.
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'strawberry', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'pineapple', color: 'yellow' },
{ name: 'grape', color: 'purple' },
{ name: 'plum', color: 'purple' }
];
function test(color) {
// використовуємо Array filter для пошуку фруктів за кольором
return fruits.filter(f => f.color == color);
}
Завжди існує понад один спосіб отримати однаковий результат. У нас було чотири варіанти.
5. Використовуйте Array.every
& Array.some
для критеріїв
Остання порада стосується використання (відносно) нових функцій масивів у JavaScript для зменшення кількості рядків коду. Погляньте на код нижче: ми хочемо перевірити чи усі фрукти червоного кольору.
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
let isAllRed = true;
// умова: усі фрукти повинні бути червоними
for (let f of fruits) {
if (!isAllRed) break;
isAllRed = (f.color == 'red');
}
console.log(isAllRed); // false
}
Цей код такий довгий! Ми можемо зменшити кількість рядків з Array.every
:
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// умова: короткий спосіб, усі фрукти повинні бути червоними
const isAllRed = fruits.every(f => f.color == 'red');
console.log(isAllRed); // false
}
Тепер код виглядає чистішим. Реалізуємо в один рядок перевірку умови з Array.some
.
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// умова: якщо якийсь фрукт червоний
const isAnyRed = fruits.some(f => f.color == 'red');
console.log(isAnyRed); // true
Ще немає коментарів