Мои любимые трюки в JavaScript +14




Приветствую. Представляю вашему вниманию перевод статьи «My Favorite JavaScript Tips and Tricks», опубликованной 28 июля 2020 года автором Tapas Adhikary



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


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


1. Забудьте о конкатенации, используйте шаблонные строки (литералы)


Конкатенация строк с помощью оператора "+" – это старая школа. Более того, конкатенация строк с участием большого количества переменных (или выражений) повышает риск возникновения путаницы и ошибок.


let name = 'Charlse';
let place = 'India';
let isPrime = bit => {
  return (bit === 'P' ? 'Prime' : 'Nom-Prime');
}

// конкатенация строк с помощью оператора "+"
let messageConcat = 'Mr. ' + name + ' is from ' + place + '. He is a' + ' ' + isPrime('P') + ' member.'

Шаблонные строки (или литералы) позволяют встраивать выражения прямо в текст. Они обладают уникальным синтаксисом, при котором строка заключается в обратные кавычки (``). Шаблонная строка может содержать места для подстановки динамических значений. Такие места отмечаются знаком доллара и фигурными скобками. Например, ${выражение}.


Демонстрация их применения:


let name = 'Charlse';
let place = 'India';
let isPrime = bit => {
  return (bit === 'P' ? 'Prime' : 'Nom-Prime');
}

// использование шаблонной строки
let messageTemplateStr = `Mr. ${name} is from ${place}. He is a ${isPrime('P')} member.`
console.log(messageTemplateStr);

2. isInteger


Это аккуратный способ узнать, является ли значение целым числом. Встроенный в JavaScript API "Number" предоставляет для этого метод "isInteger()". Очень полезная штука, о которой следует знать.


let mynum = 123;
let mynumStr = "123";

console.log(`${mynum} is a number?`, Number.isInteger(mynum));
console.log(`${mynumStr} is a number?`, Number.isInteger(mynumStr));

Результат:



3. Значение как число


Вы когда-нибудь обращали внимание, что "event.target.value" всегда возвращает строковое значение, даже если для поля ввода "input" задан тип "number"?


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


<input type='number' onkeyup="trackChange(event)" />

Обработчик извлекает значение поля с помощью "event.target.value". Но возвращаемое значение имеет строковый тип. Мы получим дополнительную головную боль из-за необходимости преобразовывать это значение в целое число. А что, если поле ввода допускало бы и дробные числа (типа 16.56)? Тогда для преобразования пришлось бы использовать "parseFloat()"? Сколько же лишней работы и риска что-то напутать!


function trackChange(event) {
   let value = event.target.value;
   console.log(`is ${value} a number?`, Number.isInteger(value));
}

Чтобы сразу получать числовое значение, используйте "event.target.valueAsNumber".


let valueAsNumber = event.target.valueAsNumber;
console.log(`is ${value} a number?`, Number.isInteger(valueAsNumber));


4. Сокращение с помощью AND


Давайте рассмотрим ситуацию, в которой у нас есть логическое значение и функция.


let isPrime = true;
const startWatching = () => {
    console.log('Started Watching!');
}

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


if (isPrime) {
    startWatching();
}

А как насчёт использования сокращённой записи вместе с оператором AND (&&)? Да, условный оператор "if" нам больше не нужен. Круто, правда?


isPrime && startWatching();

5. Значение по умолчанию с помощью OR


Если для переменной необходимо предусмотреть запасное значение по умолчанию, это достаточно просто реализуется с помощью оператора OR.


let person = {name: 'Jack'};

// если свойство "age" равно "undefined", устанавливает значение 35
let age = person.age || 35;

console.log(`Age of ${person.name} is ${age}`);

6. Произвольные значения


Генерирование произвольных значений или получение произвольного элемента массива – очень полезные методы, которые стоит держать под рукой. Я использую их почти в каждом своём проекте.


Получить произвольный элемент из массива


let planets = ['Mercury ', 'Mars', 'Venus', 'Earth', 'Neptune', 'Uranus', 'Saturn', 'Jupiter'];
let randomPlanet = planets[Math.floor(Math.random() * planets.length)];
console.log('Random Planet', randomPlanet);

Генерирование произвольного числа из диапазона с указанием минимального и максимального значений


 let getRandom = (min, max) => {
     return Math.round(Math.random() * (max - min) + min);
 }
 console.log('Get random', getRandom(0, 10));

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

7. Значения параметров функции по умолчанию


В JavaScript параметры функции подобны локальным переменным. При вызове этой самой функции вы можете и не передавать значения для её параметров. В этом случае они принимают значение "undefined", что может привести к нежелательным последствиям.


Существует простой способ передачи значения по умолчанию для параметров функции при их определении. В примере ниже для параметра "message" функции "greetings" передаётся значение по умолчанию "Hello".


let greetings = (name, message='Hello,') => {
    return `${message} ${name}`;
}

console.log(greetings('Jack'));
console.log(greetings('Jack', 'Hola!'));

8. Обязательные параметры функции


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


Сначала объявляем функцию, которая будет выдавать в консоль сообщение об ошибке


let isRequired = () => {
    throw new Error('This is a mandatory parameter.');
}

Затем эту функцию присваиваем как значение по умолчанию для параметров, которые хотим сделать обязательными. Помните, что если при вызове функции её параметр равен "undefined", подставляется значение по умолчанию, в обратном случае значение по умолчанию игнорируется.


let greetings = (name=isRequired(), message='Hello,') => {
    return `${message} ${name}`;
}
console.log(greetings());

В примере выше параметр "name" будет иметь значение "undefined", из-за чего будет произведена попытка установить значение по умолчанию, которым и выступает функция "isRequired()". Будет вызвана ошибка:



9. Оператор "Запятая"


Я столько раз использовал запятую в коде, но не осознавал, что она является отдельным оператором.


В JavaScript оператор запятой используется для оценки каждого из операндов слева направо и возврата значения последнего операнда.


let count = 1;
let ret = (count++, count);
console.log(ret);

В примере выше значением переменной "ret" будет число 2. По тому же принципу, результатом следующего кода будет вывод в консоль числа 32.


let val = (12, 32);
console.log(val);

Где мы его используем? Есть идеи? Чаще всего оператор запятой используется параметров в цикле "for".


В примере ниже оператор запятой задаёт значение переменной "j" после объявления счётчика "i".


for (var i = 0, j = 50; i <= 50; i++, j--)

10. Объединение нескольких объектов


У вас может возникнуть потребность объединить вместе два объекта, чтобы создать третий, более полный. В этом случае можно использовать оператор "..." (да, три точки).


Рассмотрим на примере:


let emp = {
 'id': 'E_01',
 'name': 'Jack',
 'age': 32,
 'addr': 'India'
};

let job = {
 'title': 'Software Dev',
  'location': 'Paris'
};

Их можно объединить с помощью spread-оператора (оператора расширения):


 // spread-оператор
 let merged = {...emp, ...job};
 console.log('Spread merged', merged);

Существует и другой путь такого объединения. С помощью "Object.assign()":


 console.log('Object assign', Object.assign({}, emp, job));

В результате получается:



Обратите внимание, что и spread-оператор и "Object.assign" выполняют поверхностное (shallow) объединение. При поверхностном объединении, если свойства повторяются, то происходит перезапись первого объекта данными из таких же свойств второго.


Для глубокого объединения объектов, следует использовать, например, библиотеку lodash


11. Деструктуризация


Деструктуризация – это техника, позволяющая извлекать значения элементов массива или свойств объекта в набор переменных. Давайте рассмотрим её на примерах


Массив


У нас есть массив со смайликами



Чтобы осуществить его деструктуризацию, следует использовать следующий синтаксис:\


let [fire, clock, , watermelon] = emojis;

Это то же самое, что и "let fire = emojis[0];", но способ гораздо более гибкий. Вы обратили внимание, что я пропустил присваивание смайлика "награда", просто оставив пустое пространство между запятыми? Итак, что у нас получится?


console.log(fire, clock, watermelon);

Результат:



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


let [fruit, ...rest] = emojis;
console.log(rest);

Результат:



Объект


Подобно массивам, деструктурировать можно и объекты


let shape = {
  name: 'rect',
  sides: 4,
  height: 300,
  width: 500
};

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


let {name, sides, ...restObj} = shape;
console.log(name, sides);
console.log(restObj);

Результат



Дополнительно почитать на тему деструктуризации можно здесь.


12. Обмен значений переменных


С помощью техники деструктуризации, с которой мы ознакомились выше, это сделать очень просто


let fire = '';
let fruit = '';

[fruit, fire] = [fire, fruit];
console.log(fire, fruit);

13. isArray


Ещё один полезный метод, на этот раз позволяющий определить, являются ли входящие данные массивом


let emojis = ['', '?', '', ''];
console.log(Array.isArray(emojis));

let obj = {};
console.log(Array.isArray(obj));

14."'undefined" против "null"


"undefined" – присутствует у переменной, которая была объявлена, но значение для неё задано не было
"null" – обозначает пустое или несуществующее значение, которое явно присваивается переменной


"undefined" и "null" не равны при строгом сравнивании


undefined === null // false

Дополнительно почитать на тему разницы между этими двумя значениями можно здесь


15. Получение фрагментов url-адреса


Объект "window.location" имеет набор полезных методов и свойств. С помощью них мы можем получить данные про протокол, хост, порт, домен и тому подобное из url-адреса браузера.


Одно из свойств, которое мне кажется очень полезным


window.location.search

Свойство "search" возвращает фрагмент строки url-адреса, находящийся после вопросительного знака: "?project=js".


Для получения параметров запроса, помимо "location.search" можно использовать ещё один полезный API, называемый "URLSearchParams".


let project = new URLSearchParams(location.search).get('project');

В результате получаем "js"


Подробнее об этой теме читайте здесь


Это не конец


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


Какие у вас любимые приёмы JavaScript? Как насчёт поделиться ими в комментариях ниже?




Комментарии (27):

  1. mironoffe
    /#21928378 / +1

    Когда надо выполнить несколько методов подряд при каком-то событии на элементе — можно использовать битовый xor:

    <button @click="e => doSomething(e) ^ doSomethingElse(e)" />


    В отличии от операторов || и && он не восприимчив к возвращаемому значению и выполняет всю цепочку

    • Ilusha
      /#21928542

      Существует ли реальная необходимость в таком dirty hack?

      • mironoffe
        /#21929054

        Во vue когда функции приходят в слот через его props сложно сделать менее dirty. То есть придётся делать метод, который примет функции в аргументах и выполнит их.

    • stardust_kid
      /#21928606

      А не проще ли тогда обернуть их в отдельную функцию и вызвать внутри по очереди?

    • Zenitchik
      /#21928854 / +1

      А запятая вас чем не устроила?

      • Taraflex
        /#21931208

        Нужны будут скобки. Проблема в приоритетах

        const fn = (e)=> someWork(e), work2(e)

        тут без скобок в fn попадет значение work2(e) вместо лямбды
        const fn = (e)=> (someWork(e), work2(e))

  2. faiwer
    /#21928776 / +1

    • Не используйте хаки condition && invocation() вместо if (condition) invocation().
    • Умоляю, не используйте оператор-запятую (кроме разве что с for())
    • Не используйте ^-xor хак выше

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


    Разве что если ваша работа не сводится к написанию write-only кода… Тогда можно писать ногами.

    • NewMax
      /#21929038

      Не используйте хаки condition && invocation() вместо if (condition) invocation().

      Кстати видел довольно удобное применение этому в React для опционального рендера, примерно так:
      <div>
          {isCondition && <SomeComponent1 />}
          <SomeComponent2 />
          <SomeComponent3 />
          {isAnotherCondition && <SomeComponent4 />}
      </div>
      

      А в бизнес-логике такое смотрится довольно странно, согласен

      • k12th
        /#21929060 / +1

        С изобретением JSX количество тернарных операторов в мире увеличилось вдвое:)

        • mironoffe
          /#21929094

          Прирост компенсирует оператор опциональной последовательности

            foo && foo.bar && foo.bar.baz
            // vs
            foo?.bar?.baz
          

          • k12th
            /#21929120

            Это не исключает тернарного оператора: { foo?.bar?.baz ? <ComponentOne/> : ComponentTwo/>}

            • /#21929348

              Кошмар. И эти люди ругали Perl.

              • k12th
                /#21929368

                Я лично никогда не ругал Perl, держал свое мнение при себе:)

      • faiwer
        /#21929182

        Кстати видел довольно удобное применение этому в React

        Это потому что проектируя JSX его создатели намеренно проигнорировали:


        • условия и прочие вветвления
        • итераторы

        Аргументируют это тем, что используя {} вы можете вставить любой JS-код. Скажем всякие хаки ? A : undefined, A && B, c.map(...).


        У меня от это уже года 4 бомбит. Есть решение — jsx-control-statements. Но, к сожалению, это почти никак невозможно с TypeScript (ввиду того как устроена там поддержка plugin-ов). Поэтому пришлось съехать снова на эти && хаки.

        • k12th
          /#21929280
          • faiwer
            /#21929404 / +1

            А tsx-control-statements не может в сужение типов. Условно:


            <If condition={optionalField in obj}>
              {obj.optionalField /* error */}
            </If>

            Для него это просто обёртка.


            А ещё tsx-control-statements не умеет в удобные <For each="element" of={source}/>. Причину описал выше — TS как language server не даёт возможности патчить TS код до вывода типов. Его плагины умеют только в патчинг выходного JS кода

        • NewMax
          /#21929478

          ИМХО меня в реакте как раз и привлекает концепция, что пишешь обычный JS код с HTML-подобным синтаксисом для разметки.
          Когда смотрел в сторону ангуляра, эти различные

          <div *ngIf="condition">Content to render when condition is true.</div>

          как-то отпугнули.
          Да, в реакте вроде как выглядит похоже
          <Component prop={value} />

          но для меня разница больше ментальная, так как в случае ангуляра добавляются кастомные данные в обычные html теги (о нет, только не это), а в реакте — в кастомные компоненты, для них вроде как можно :)
          Ну и из таких же соображений я не переходил на реактовые хуки, их работа _не очевидна_ со стороны

          • faiwer
            /#21929696

            Когда смотрел в сторону ангуляра, эти различные *ngIf как-то отпугнули

            Меня всегда удивляли эти нападки на синтаксис vue, angular, svelte. Это как всерьёз обсуждать форму ручек дверей внедорожников. Сложные и важные вещи, которые и определяют всё, лежат далеко вне плоскости этого синтаксического сахара. Важно то как оно работает. Как там потоки данных гуляют. Как и чем обеспечивается реактивность. Какие проблемы и сколь эффективно решает инструмент. Я так в своё время отказался от Vue2. У него очень приятный шаблонизатор, но что от него толку, с такой моделью observable. Я могу сколь угодно восхищаться их вкусностями в директивах вроде .prevent и .keyUp.esc, но пока оно не умеет в прямые зависимости одних computed значений от других, я лучше выберу другой инструмент.


            Ну и из таких же соображений я не переходил на реактовые хуки, их работа не очевидна со стороны

            Ну так можно долго бегать. Бегать от ФП, от хуков, от контейнеризации, от… от чего угодно незнакомого на самом деле. Кто-то даже от GIT-а бегает и заливает обновы по FTP. Я с большим удивлением для себя открыл что очень многие мобильные разработчики до сих пор делают руками, то что в web-е реактивно уже лет 5-10. Показываешь им магию реактивности — морщат носы и отнекиваются. Мол это медленно, это непонятно, нам так нельзя, мы лучше по старинке будет всё руками писать. Моя их не понимать.

        • St1ggy
          /#21937504

          Не согласен, что jsx-control-statements хорош. Если ректорский && попросту не вызывает правую часть выражения при отрицательном условии, то эти все обертки с If и т.д. вызывают компонент, в котором проверяется условие. Лучше проверить и не вызывать, чем вызывать, проверить и не вызывать.

          • faiwer
            /#21938040

            Лучше проверить и не вызывать, чем вызывать, проверить и не вызывать.

            Хех. Не вызывает он ничего. Если бы вызывал, никому бы он нафиг не сдался, уж поверьте. Это babel-plugin, который как раз <If/>-ы в эти хаки с && и преобразует. И именно поэтому у него проблемы с TypeScript, т.к. оный просто не умеет в плагины трансформации TS AST.

      • DmitryKazakov8
        /#21929240

        Ну только не {isCondition && <SomeComponent1 />} конечно, а {Boolean(isCondition) && <SomeComponent1 />}, так как этот оператор приводит к boolean лишь при сравнении, но выводит в итоге оригинальное значение. Была в одном известном проекте система прав и пермишенов, основанная на 0 и 1 вместо булеанов, то есть isCondition был при отсутствии прав === 0, соответственно на странице оказывалось немало нод-нулей, отчего ехала верстка. А так как разработчиков было много, пришлось очень тщательно следить за этим на код-ревью.

        • Dron007
          /#21931486

          {!!isCondition && <SomeComponent1 />} покомпактнее будет тогда и ещё один "трюк". Но лучше типы по назначению использовать всё-таки.

  3. k12th
    /#21929050 / +1

    А что, если поле ввода допускало бы и дробные числа (типа 16.56)? Тогда для преобразования пришлось бы использовать "parseFloat()"?

    Можно всегда использовать parseFloat. Number.isInteger(parseFloat('15.0')) возвращает true.


    event.target.valueAsNumber

    На картинке рядом видно, что еще бывает .valueAsDate (для <input type="date"/> и т.д.)

  4. arh11msn
    /#21929402

    В 6 примере лучше не писать велосипед, а использовать lodash.sample(planets)

  5. superD
    /#21930556

    Мои любимые трюки в JavaScript

    Трюков тут от силы штук 5, остальное — возможности языка из документации (isArray, destructuring, spread operator, шаблонный строки и др.). Еще бы написали про «трюк» как создать переменную…

  6. Alexandroppolus
    /#21931238

    5 пункт: пора уже привыкать к "??", по крайней мере там где ts или бабель