furry.cat 12 января 2021

☕ 33 приема оптимизации JavaScript, которые вы должны знать в 2021 году

furry.cat

Frontend-разработчик в Foquz. https://www.cat-in-web.ru/
Предлагаем вашему вниманию подборку полезных техник и хитростей для оптимизации кода на JavaScript. Она будет интересна не только изучающим язык новичкам, но и опытным программистам.
☕ 33 приема оптимизации JavaScript, которые вы должны знать в 2021 году

Хороший разработчик всегда должен учиться чему-то новому и совершенствовать навыки, которые у него уже есть. Поэтому сейчас мы будем учиться писать более чистый и понятный код!

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

В конце статьи приведен список современных возможностей языка, разделенный на версии.

Объявление переменных и присваивание значений

1. Объявление нескольких переменных

Переменные можно объявлять через запятую, при этом не требуется повторно использовать инструкцию let:

        // Длинно
let test1 = 1;
let test2 = 2;

// Коротко
let test1 = 1, test2 = 2;
    

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

        let test1, test2, test3, test4, test5;
    

2. Арифметические операции

Присваивание в JavaScript легко совмещается с арифметическими операторами:

        // Длинно
test1 = test1 + 1;
test2 = test2 - 1;
test3 = test3 * 20;

// Коротко
test1++;
test2--;
test3 *= 20;
    

3. Присваивание значений нескольким переменным

Если требуется присвоить значения сразу нескольким переменным, мы обычно делаем так:

        let test1, test2, test3;
test1 = 1;
test2 = 2;
test3 = 3;
    

Но всю эту логику можно свести в одну строку, если использовать деструктуризацию:

        let [test1, test2, test3] = [1, 2, 3];
    

Синтаксис деструктуризации работает не только с массивами, но и с обычными объектами:

        const data = {
  test1: 1,
  test2: 2,
  test3: 3
};

// Длинно
const test1 = data.test1;
const test2 = data.test2;
const test2 = data.test3;

//shorthand
const { test1, test2, test3 } = data;
    

4. Присваивание со значением по умолчанию

Иногда требуется положить в переменную какое-то значение, предварительно убедившись, что это значение существует и не является falsy (null, undefined, пустая строка, 0).

Для этого можно использовать обычную конструкцию if-else:

        let test2;
if (test1) {
  test2 = test1;
} else {
  test2 = 'default value';
}
    

Можно написать короче.

Взять, например, логический оператор ||. Если в переменной находится truthy-значение, то будет возвращено оно, иначе – дефолтное значение с правой стороны оператора.

        let test 2 = test1 || 'default value';
    

Еще более современный подход – nullish coalescing operator??. Однако он проверяет не на все falsy-значения, а только на null и undefined. В остальном логика такая же, как у оператора ||.

        const test = null ?? 'default value';
console.log(test); // 'default value'

const test1 = 0 ?? 2;
console.log(test1); // 0
    

Условия

5. Автоматическая проверка на truthy и falsy значения

Иногда требуется проверить, есть ли в переменной какое-либо значение. При этом важно учитывать и null, и undefined, и другие falsy-значения (NaN, пустая строка, 0).

        if (test1 !== null || test1 !== undefined || test1 !== '') {
    // logic
}
    

К счастью, нет необходимости проводить все проверки по отдельности, тем более, что при этом легко что-нибудь упустить. Например, 0, который в ряде ситуаций тоже может расцениваться как "пустое" значение.

Можно просто положиться на JavaScript и его динамическую конверсию типов.

        if (test1) {
  // logic
}
    

Оператор if самостоятельно приведет переменную к логическому значению и осуществит проверку.

6. Логические операторы вместо if

В пункте Присваивание с дефолтным значением мы уже видели, как логический оператор (||) может заменить конструкцию if.

Оператор && также способен на многое:

        // Длинно
if (test1) {
 callMethod(); 
} 
// Коротко 
test1 && callMethod();
    

Если test1 является falsy-значением, то до инструкции callMethod() выполнение не дойдет.

7. Тернарный оператор вместо if

Зачастую простые конструкции if-else можно заменить еще более простым тернарным оператором:

        // Длинно
let test: boolean;
if (x > 100) {
    test = true;
} else {
    test = false;
}

// Коротко
let test = (x > 10) ? true : false;

// Еще короче
let test = x > 10;
    

Таким образом можно преобразовать и более сложные условия, даже вложенные:

        let x = 300,
test2 = (x > 100) 
        ? 'greater 100' 
        : (x < 50) 
           ? 'less 50' 
           : 'between 50 and 100';

console.log(test2); // "greater than 100"
    

Важно: чем сложнее условие, тем запутаннее выглядит тернарный оператор и предпочтительнее – стандартная конструкция if-else.

8. Проверка на значение из набора

Если требуется выполнить какие-то действия, когда x равен одному из нескольких значений, первым порывом может быть использование обычных сравнений:

        if (x === 'abc' || x === 'def' || x === 'ghi' || x ==='jkl') {
    //logic
}
    

Но чем больше таких "подходящих" значений, тем сильнее разрастается условие и тем проще сделать ошибку. Проще поместить все эти значения в массив и использовать метод Array.prototype.includes для проверки:

        if (['abc', 'def', 'ghi', 'jkl'].includes(x)) {
   //logic
}
    

Важно: метод includes использует строгое сравнение с учетом типа аргумента.

9. Коллекция вместо switch

Вместо того, чтобы рассматривать каждый случай внутри инструкции switch, можно добавить их все в объект:

        // Длинно
switch (data) {
  case 1:
    test1();
  break;

  case 2:
    test2();
  break;

  case 3:
    test();

  break;
  // ...
}

// Коротко
const collection = {
  1: test1,
  2: test2,
  3: test
};

collection[data] && collection[data]();
    

Циклы

10. Короткий синтаксис for

Всем знакомый старый-добрый цикл for имеет довольно громоздкую структуру:

        for (var i = 0; i < testData.length; i++) {
  // i - индекс элемента
  // доступ к значению элемента i
  var item = testData[i];
}
    

К счастью, в последних версиях языка появилось несколько более удобных альтернатив:

        for (let i in testData) {
  // i - индекс элемента
  // доступ к элементу i
  var item = testData[i];
}

for (let i of testData) {
  // i - значение элемента
}
    

Кроме того, есть замечательный функциональный метод forEach:

        function testData(element, index, array) {
  console.log('test[' + index + '] = ' + element);
}

[11, 24, 32].forEach(testData);
// logs: test[0] = 11, test[1] = 24, test[2] = 32
    

Такой код выглядит очень понятно, но у него есть некоторые ограничения. Например, перебор массива методом forEach нельзя прервать с помощью инструкции break.

Числа

11. Конверсия строки в число

Одиночный оператор сложения (+) неявно приводит тип полученного аргумента к числу.

        // Длинно
let test1 = parseInt('123'); 
let test2 = parseFloat('12.3'); 

// Коротко
let test1 = +'123'; 
let test2 = +'12.3';
    

12. Экспоненциальная запись

Избавиться от большого количества нулей поможет экспоненциальная запись:

        // Длинно
for (var i = 0; i < 10000; i++) { ... }

// Коротко
for (var i = 0; i < 1e4; i++) { ... }
    

13. Возведение в степень

Если вы вдруг до сих пор не знали, то пришло время узнать – в JavaScript есть специальный оператор для возведения в степень:

        // Длинно
Math.pow(2,3); 

// Коротко
2**3 
    

14. Округление

Двойное побитовое отрицание для 32-битных целых чисел дает такой же эффект, как Math.floor (округляет вниз):

        // Длинно
Math.floor(1.9) === 1 // true

// Коротко
~~1.9 === 1 // true
    

Строки

15. Получение символа из строки

Строка – это по сути массив символов, поэтому к каждому из них можно обратиться по индексу:

        let str = 'abc';

// Длинно
str.charAt(2); // c

// Коротко
str[2]; // c
    

16. Повторение строки

Чтобы соединить несколько одинаковых строк в одну, существует несколько подходов. Самый очевидный – и самый громоздкий – использовать цикл:

        let test = ''; 
for(let i = 0; i < 5; i ++) { 
  test += 'test '; 
} 
    

Но есть и более логичный – встроенный метод String.prototype.repeat():

        let test = 'test '.repeat(5);

    

Бонус для любителей нестандартных решений:

        let test = Array(6).join('test ');

    

17. Конкатенация

Составление одной строки из нескольких фрагментов – ужасная головная боль. Нас спасут шаблонные литералы:

        // Длинно
const welcome = 'Hi ' + test1 + ' ' + test2 + '.'

// Коротко
const welcome = `Hi ${test1} ${test2}.`;
    

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

        // Длинно
const data = 'abc abc abc abc abc abc\n\t'
    + 'test test,test test test test\n\t'

// Коротко
const data = `abc abc abc abc abc abc
         test test,test test test test`
    

Массивы

18. Наличие элемента

Обычно чтобы проверить, присутствует ли элемент в массиве, мы используем метод indexOf и сравниваем найденный индекс с -1.

        if (arr.indexOf(item) > -1) { 
  // элемент в массиве есть
}

if(arr.indexOf(item) === -1) { 
  // элемента в массиве нет
}
    

Но можно использовать другой подход, без сравнения индексов – побитовый оператор ~.

        if(~arr.indexOf(item)) { 
  // элемент в массиве есть
}

if(!~arr.indexOf(item)) { 
  // элемента в массиве нет
}
    

Оператор ~ возвращает 0 только для значения -1. Для всех других значений будет возвращено число, отличное от нуля, то есть truthy-значение.

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

Не стоит забывать и про удобнейший метод Array.prototype.includes:

        if (arr.includes(item)) { 
  // элемент в массиве есть
}

    

19. Поиск элемента

Метод Array.prototype.find – это удобная функциональная замена простому перебору элементов массива с помощью цикла for:

        // Длинно
function findElementByName(arr, name) {
    for (let i = 0; i < arr.length; ++i) {
        if (arr[i].name === name) {
            return arr[i];
        }
    }
}

// Коротко
function findElementByName(arr, name) {
  return arr.find(el => el.name === name);
}

const data = [
  { name: 'John' },
  { name: 'Jane' },
]
findElementByName(data, 'Jane'); // { name: 'Jane' }
    

20. Spread-синтаксис

Spread-синтаксис был введен в язык специально для облегчения множества операций со сложными структурами данных.

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

        // Стандартный подход
const data = [1, 2, 3];
const test = [4, 5, 6].concat(data);

// Spread-синтаксис
const data = [1, 2, 3];
const test = [4, 5, 6, ...data];

console.log(test); // [ 4, 5, 6, 1, 2, 3]
    

Или клонирования массива:

        // Стандартный подход
const test1 = [1, 2, 3];
const test2 = test1.slice()

// Spread-синтаксис
const test1 = [1, 2, 3];
const test2 = [...test1];
    

21. Минимальное и максимальное значение

Методы Math.max и Math.min могут принимать любое количество аргументов. Чтобы передать им массив, можно использовать метод Function.prototype.apply:

        const arr = [1,2,3];
Math.max.apply(null, arr); // 3
Math.min.apply(null, arr); // 1
    

А можно воспользоваться деструктуризацией:

        Math.max(…arr); // 3
Math.min(…arr); // 1
    

Объекты

22. Присваивание значений

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

        let test1 = 'a'; 
let test2 = 'b';

// Длинно
let obj = {test1: test1, test2: test2}; 

// Коротко
let obj = {test1, test2};
    

23. Перебор ключей и значений

В современном JavaScript есть сразу 3 метода для перебора объектов: Object.keys(), Object.values() и Object.entries(). Каждый из них возвращает массив – ключей, значений или сразу и того, и другого.

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

        const data = { test1: 'abc', test2: 'cde', test3: 'efg' };

Object.keys(data); // ['test1', 'test2', 'test3'];
Object.values(data); // ['abc', 'cde', 'efg'];
Object.entries(data); // [['test1','abc'],['test2','cde'],['test3','efg']]

    

Функции

24. Параметры по умолчанию

Современный стандарт JavaScript позволяет задать дефолтные значения параметров прямо в сигнатуре функции. Теперь не нужно проверять это отдельно:

        // Длинно
function add(test1, test2) {
  if (test1 === undefined)
    test1 = 1;
  if (test2 === undefined)
    test2 = 2;
  return test1 + test2;
}

// Коротко
function add(test1 = 1, test2 = 2) {
  return test1 + test2;
}


add(5, 10); // 15
add(5); // 7
add(); // 3

add(5, null); // 5


    

Важно: дефолтное значение устанавливается только в том случае, если указанный параметр не передан или равен undefined. На null это не распространяется.

25. Операции в return

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

        // Длинно
function check(test) {
  if (!test) {
    return 'default value';
  } else {
    return test;
  }
}

// Коротко 
function check(test) {
  return test || 'default value';
}
    

26. Стрелочные функции

В ряде случаев стрелочные функции могут стать более короткой и удобной альтернативой обычным. Например, их очень удобно использовать для коллбэков:

        // Длинно
[1, 2, 3].map(function(i) { 
  return i * 2;
});

// Коротко
[1, 2, 3].map(i => i * 2);
    

Стрелочные функции позволяют возвращать значение неявно, без использования оператора return:

        // Длинно
function calculate(diameter) {
  return Math.PI * diameter
}

// Коротко
calculate = diameter => Math.PI * diameter;
    

Важно: не злоупотребляйте стрелочными функциями из-за их краткости, используйте их по прямому назначению, например, для сохранения контекста выполнения.

***

Сокращения в программировании похожи на лекарства. В небольших дозах они полезны и делают вашу жизнь лучше, но при передозировке возможно отравление и другие неприятные побочные эффекты.

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

***

Современные возможности JavaScript

ES2021/ES12

ES2020/ES11

ES2019/ES10

ES2018/ES9

ES2017/ES8

ES2016/ES7

Источники

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Android разработчик
от 120000 RUB до 200000 RUB
Junior QA engineer
Томск, от 45000 RUB до 60000 RUB
Senior Data Scientist
Москва, от 300000 RUB до 450000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ