Малоизвестные возможности JavaScript

JavaScript часто называют самым простым языком для новичков, в программировании на котором сложнее всего достичь мастерства. Автор материала говорит, что не может не согласиться с этим утверждением. Всё дело в том, что JS — это по-настоящему старый и по-настоящему гибкий язык. Он полон таинственных синтаксических конструкций и устаревших возможностей, всё ещё им поддерживаемых.

Сегодня мы поговорим о малоизвестных возможностях JavaScript и о вариантах их практического применения.

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

JavaScript — это всегда что-то новое

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

Код, который мы тут будем обсуждать, можно найти здесь

Обратите внимание на то, что я не включил сюда такие вещи, как поднятие переменных, замыкания, прокси-объекты, прототипное наследование, async/await, генераторы и прочее подобное. Хотя эти особенности языка и можно отнести к сложным для понимания, малоизвестными они не являются.

Оператор void

В JavaScript имеется унарный оператор void. Возможно, вы сталкивались с ним в виде void(0) или void 0. Его единственная цель — вычислить выражение, находящееся справа от него и вернуть undefined. 0 тут используется просто потому что так принято, хотя это и необязательно, и тут можно использовать любое правильное выражение. Правда, этот оператор в любом случае вернёт undefined.

Зачем добавлять в язык особое ключевое слово, служащее для возврата undefined, если можно просто воспользоваться стандартным значением undefined? Не правда ли, тут ощущается некоторая избыточность?

Как оказалось, до появления стандарта ES5 в большинстве браузеров стандартному значению undefined можно было присвоить новое значение. Скажем, можно было успешно выполнить такую команду: undefined = "abc". В результате значение undefined могло оказаться совсем не тем, чем оно должно быть. В те времена использование void позволяло обеспечить уверенность в использовании именно настоящего undefined.

Скобки при вызове конструкторов необязательны

Скобки, которые добавляют после имени класса, вызывая конструктор, совершенно необязательны (если только конструктору не надо передавать аргументы).

В следующем примере наличие или отсутствие скобок на правильность работы программы не влияет.

Скобки при работе с IIFE можно не использовать

Синтаксис IIFE всегда казался мне странноватым. Зачем тут все эти скобки?

Как оказалось, скобки нужны лишь для того, чтобы сообщить JavaScript-парсеру о том, что некий код представляет собой функциональное выражение, а не неправильную попытку объявления функции. Знание этого факта позволяет понять то, что есть множество способов избавиться от скобок, в которые заключают IIFE, и при этом написать работающий код.

Здесь оператор void сообщает парсеру о том, что следующий за ним код является функциональным выражением. Это даёт возможность избавиться от скобок вокруг объявления функции. И, кстати, тут можно использовать любой унарный оператор (void, +!, -, и так далее), и код останется рабочим. Разве это не замечательно?

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

Скобки вокруг первого IIFE лишь улучшают читабельность кода, не влияя на его работу.

Если вы хотите лучше разобраться с IIFE — взгляните на этот материал.

Конструкция with

Знаете ли вы о том, что в JavaScript имеется конструкция with, поддерживающая блоки выражений? Выглядит это так:

Конструкция with добавляет все свойства переданного ей объекта в цепочку областей видимости, используемую при выполнении команд.

Может показаться, что with — это замечательный инструмент. Похоже, что он даже лучше чем новые возможности JS по деструктурированию объектов, но на самом деле это не так.

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

Конструктор Function

Использование ключевого слова function — это не единственный способ определить новую функцию. Определять функции можно динамически, используя конструктор Function и оператор new. Вот как это выглядит.

Последний аргумент, передаваемый конструктору, представляет собой строку с кодом функции. Два других аргумента — это параметры функции.

Интересно отметить, что конструктор Function является «родителем» всех конструкторов в JavaScript. Даже конструктор Object — это конструктор Function. И собственный конструктор Function — это тоже Function. В результате, вызов вида object.constructor.constructor..., выполненный для любого JS-объекта достаточное количество раз вернёт в итоге конструктор Function.

Свойства функций

Все мы знаем, что функции в JavaScript являются объектами первого класса. Следовательно, никто не мешает нам добавлять в функции новые свойства. Это совершенно нормально, но используется подобное редко.

Когда это может понадобиться?

На самом деле, есть несколько ситуаций, в которых эта возможность функций может оказаться кстати. Рассмотрим их.

Настраиваемые функции

Предположим, у нас имеется функция greet(). Нам нужно, чтобы она выводила бы разные приветственные сообщения в зависимости от применяемых региональных настроек. Эти настройки могут храниться в некоей внешней по отношению к функции переменной. Кроме того, в функции может быть свойство, определяющие эти настройки, в частности — настройки языка пользователя. Мы воспользуемся вторым подходом.

Функции со статическими переменными

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

Но что если нам нужна гибкость, если требуется читать или даже модифицировать значение подобного счётчика и при этом не засорять глобальную область видимости?

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

Свойства объекта arguments

Уверен, большинство из вас знают о том, что в функциях имеется объект arguments. Это — массивоподобный объект, доступный внутри всех функций (за исключением стрелочных, у которых нет собственного объекта arguments). Он содержит список аргументов, переданных функции при её вызове. Кроме того, в нём имеются некоторые интересные свойства:

  • arguments.callee содержит ссылку на текущую функцию.
  • arguments.caller содержит ссылку на функцию которая вызвала текущую функцию.

Рассмотрим пример.

Стандарт ES5 запрещает использование свойств callee и caller в строгом режиме, но они всё ещё широко встречаются во многих скомпилированных в JavaScript текстах программ, например — в библиотеках. Поэтому о них полезно знать.

Тегированные шаблонные литералы

Наверняка вы, если имеете хоть какое-то отношение к программированию на JavaScript, слышали о шаблонных литералах. Шаблонные литералы — это одно из многих замечательных новшеств стандарта ES6. Однако знаете ли вы о тегированных шаблонных литералах?

Тегированные шаблонные литералы позволяют разработчику управлять тем, как шаблонный литерал превращается в строку. Делается это путём использования особых тегов. Тег — это всего лишь имя функции-парсера, которая получает массив строк и значений, интерпретированных строковым шаблоном. При использовании теговой функции ожидается, что она вернёт готовую строку.

В следующем примере наш тег — highlight, интерпретирует данные шаблонного литерала и внедряет эти данные в готовую строку, помещая их в HTML-тег <mark>, чтобы выделить их при выводе такого текста на веб-страницу.

Интересные способы использования этой возможности можно найти во многих библиотеках. Вот несколько примеров:

  • styled-components — для использования в React-приложениях.
  • es2015-i18n-tag — для перевода и интернационализации проектов.
  • chalk — для вывода в консоль разноцветных сообщений.

Геттеры и сеттеры в стандарте ES5

JavaScript-объекты, по большей части, довольно просты. Предположим, у нас имеется объект user, и мы пытаемся обратиться к его свойству age, используя конструкцию user.age. При таком подходе, если это свойство определено, мы получим его значение, а если не определено — получим undefined. Всё очень просто.

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

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

Геттеры и сеттеры не относятся к новшествам стандарта ES5. Они всегда присутствовали в языке. В ES5 лишь добавлены удобные синтаксические средства для работы с ними. Подробности о геттерах и сеттерах можно почитать здесь.

Среди примеров использования геттеров можно отметить популярную Node.js-библиотеку Colors.

Эта библиотека расширяет класс String и добавляет в него множество методов-геттеров. Это позволяет преобразовывать строку в её «раскрашенный» вариант для того, чтобы эту строку потом использовать при логировании. Делается это путём работы со свойствами строки.

Оператор «запятая»

В JS есть оператор «запятая». Он позволяет записывать в одной строке несколько выражений, разделённых запятой, и возвращать результат вычисления последнего выражения. Вот как выглядят подобные конструкции.

Здесь будут вычислены значения всех выражений, после чего в переменную result попадёт значение выражения expressionN.

Вполне возможно, что вы уже пользовались оператором «запятая» в циклах for.

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

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

Оператор «плюс»

Если вам нужно быстро превратить строку в число — вам пригодится оператор «плюс». Он способен работать с самыми разными числами, а не только, как может показаться, с положительными. Речь идёт об отрицательных, восьмеричных, шестнадцатеричных числах, и о числах в экспоненциальной записи. Более того, он способен преобразовывать во временные метки объекты Date и объекты библиотеки Moment.js.

Двойной восклицательный знак

Надо отметить, что то, что иногда называют «оператор двойной восклицательный знак» (Bang Bang или Double Bang), на самом деле, не является оператором. Это — оператор «логическое НЕ», или оператор логического отрицания, выглядящий как восклицательный знак, повторённый два раза. Двойной восклицательный знак хорош тем, что позволяет конвертировать любое выражение в логическое значение. Если выражение, с точки зрения JS, истинно — после обработки его двойным восклицательным знаком будет возвращено true. В противном случае будет возвращено false.

Оператор побитового отрицания

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

Когда этот оператор применяется к числам, он преобразует их следующим образом: из числа Nполучается -(N+1). Такое выражение даёт 0 в том случае если N равно -1.

Эту возможность можно использовать с методом indexOf() при выполнении с его помощью проверки существования в массиве или в строке некоего элемента, так как этот метод, не находя элемент, возвращает -1.

Тут надо отметить, что в стандартах ES6 и ES7, соответственно, у строк и массивов, появился метод includes(). Он, определённо, куда удобнее для определения наличия элементов, чем использование оператора побитового отрицания и indexOf().

Именованные блоки

В JavaScript есть концепция меток, используя которую, можно назначать имена (метки) циклам. Затем можно использовать эти метки для обращения к соответствующему циклу при применении инструкций break или continue. Метки можно назначать и обычным блокам кода.

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

Обратите внимание на то, что, в отличие от некоторых других языков, в JS нет инструкции goto. В результате метки используются лишь с инструкциями break и continue.

Итоги

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

Перевод статьи Little known features of JavaScript

Рекомендую подписаться на наш telegram канал WebDEV. Каждый день размещаем интересные статьи, делаем обзор JavaScript-библиотек и полезных плагинов для редакторов кода.