Пишем производительный JavaScript. 3 совета.
Эта статья основана на советах Бенедикта Маурера, встреченных мной в статьях, комментариях и лекциях. Почему эта информация интересна и важна? Бенедикт принимает непосредственное участие в разработке V8 и досконально знает, как именно интерпретатор и компилятор обрабатывают наш код.
Помогите JavaScript понять, что на самом деле вы хотите сделать
Избегайте неопределённости. Например, если у вас есть значение obj
, которое может быть undefined
или объектом, рассмотрите возможность исключения неопределенности через явное условие:
if (obj !== undefined) {
// …
}
Тогда компилятору не придётся делать множество лишних проверок, как в случае, если вы напишите:
if (obj) {
// …
}
Что должен сделать компилятор в этом случае? Проверить, что obj
не является пустой строкой, false
, 0
или undefined
. Всё это порождает лишние проверки в байткоде. Кажется, что достаточно понимания того, что перед нами объект, а объект всегда возвращает true
на toBoolean
. Но, к сожалению, в нашем динамически типизируемом языке компилятору придётся следить за всем циклом жизни переменной obj
, чтобы убедиться, что на вход условия действительно пришёл объект.
Дополнительная польза: когда вы будете читать свой код через год (или это будет делать кто-то другой), вам будет гораздо проще понять, что на самом деле проверяет ваше условие.
Осторожнее с && и ||
Это пример из реального коммита в репозитории preact. Изменение логического выражения на тернарный оператор сделано для повышения производительности кода (автор сообщает об 1–5% ускорении). Но за счёт чего?
Мы помогли компилятору, указав, что значением vlen
всегда будет Number
. В негативном сценарии первого случая мы можем получить для vlen
любой тип данных, приводимый к false
, например, Boolean
или null
, что и приводит к деоптимизации, а так же добавляет ещё одну дорогостоящую проверку позже, чтобы убедиться, что vlen
на самом деле число.
Воспользовавшись советом из первой части статьи, можно получить дополнительное ускорение:
vlen = (vchildren !== undefined) ? vchildren.length : 0
В целом, использование &&
или ||
в небулевом контексте (особенно с числами) не слишком хорошее решение, из-за семантики &&
и ||
в JavaScript.
Ещё один интересный пример неправильного использования ||
:
function foo(a, b) {
a = a || "value";
b = b || 4;
// …
}
В данном случае ошибочно отсекаются допустимые значения, такие как пустая строка ''
для a
и 0
для b
.
Не доверяйте undefined
Выше мы использовали проверку
if (obj !== undefined) {
// …
}
Но всё ли с ней хорошо? Давайте проведём небольшой эксперимент
const isDefined = (function() {
const undefined = 1;
return x => x !== undefined;
})();console.log(isDefined(undefined)); // true
console.log(isDefined(1)); // false
На самом деле undefined
не является ключевым словом в JavaScript. Это просто поле в глобальном объекте, и движок JS вынужден это учитывать. К счастью, в V8 уже присутствует неплохая оптимизация и если в цепочке видимости отсутствуют eval
или with
, компилятор не будет производить поиск значения undefined
в глобальном объекте. Но вот в браузере Safari такая оптимизация отсутствует.
Защититься от подмены undefined
(и немного упростить жизнь компилятору) можно путём вызова оператора void
, который всегда возвращает настоящий undefined
.
if (obj !== void 0) {
// …
}
Я не призываю использовать эту конструкцию, потому что она может поставить в тупик менее опытных коллег, столкнувшихся с вашим кодом. Но в случае необходимости достижения максимальной производительности стоит подумать о таком подходе. Также void 0
пригодится, если в вашем проекте включено правило no-undefined в ESlint.
Подробнее читайте в статье «Иногда undefined это defined»
Слушайте наш подкаст на SoundCloud, читайте на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.