подборка от разработчика поиска Яндекса / Блог компании Яндекс / Хабр
Привет! Меня зовут Виктор Хомяков, в Яндексе я работаю над скоростью страниц поиска. Однажды мне в голову пришла идея обобщить свой опыт и систематизировать приёмы ускорения работы кода на JavaScript. То, что получилось в итоге, собрано в этом материале.
Некоторые из приёмов будут полезны и тем, кто пишет на других языках. Все способы разделены на группы по убыванию специфичности: от наиболее общих до конкретных. Почти все примеры кода взяты из реальных проектов, из реального продакшена.
Организационные
Культура разработки performance-first
Это самое важное. Чем раньше вы начнёте контролировать скорость в вашем проекте — тем лучше для проекта. Это даст возможность заранее избежать серьёзных просчётов, которые потом будет сложно исправить.
В то же время заметьте: я не призываю сразу превращать весь код в нечитабельную «портянку». Главное — осознанно следить за ним, знать, где, что и с какой скоростью у вас работает, и осознавать, когда можно и нужно исправлять конкретные вещи и требуется ли вообще их исправлять.
Бюджет скорости
— Быть быстрее конкурентов на ≈ 20%!
— Открытие страницы в 4G сети < 3 с
— Открытие страницы в 3G сети < 5 с
— Длительность запросов за данными < 1 c
— First Contentful Paint < 1 с
— Largest Contentful Paint < 2 с
— Total Blocking Time < 500 мс
— Lighthouse Performance Score > 70
— Cumulative Layout Shift < 0,1
Очень важно определить бюджет скорости в проекте. Как именно его определять, какие метрики и какие значения выбирать — это тема отдельного длинного разговора. Главное, чтобы бюджет у вас был.
Performance mantras
1. Don’t do it
2. Do it, but don’t do it again
3. Do it less
4. Do it later
5. Do it when they’re not looking
6. Do it concurrently
7. Do it cheaperbrendangregg.com/blog/2018-06-30/benchmarking-checklist.html
Ещё один интересный подход к проблеме. Попробуйте применить семь шагов мантры к вашей проблеме скорости и производительности кода. Если один из них подскажет путь решения, смело его используйте. Такой подход работает: все приёмы, которые я дальше покажу, подпадают под какой-либо из пунктов мантры.
Следующая группа приёмов будет полезна не только в JavaScript.
Те, что можно использовать независимо от языка и его реализации
Смена языка или фреймворка
Самое главное: если вы понимаете, что ваши инструменты не подходят для данной задачи, то как можно раньше ищите другие, более подходящие. Либо вообще смените язык программирования или фреймворк. Если подходящих нет, напишите свои. Так, в конце концов, родились многие известные сейчас фреймворки и библиотеки.
Смена алгоритма
Если текущий язык вам подходит и вы его не меняете, но проблема в конкретном алгоритме, то поищите — возможно, есть алгоритмы, которые делают ту же самую задачу, но с меньшей сложностью. Например, можно попробовать перейти от O(N
2
) к O(N log N) или к O(N). Проверьте, как алгоритм работает именно с вашими данными. Возможно, данные у вас в продакшене — это наихудший вариант, в котором именно этот алгоритм показывает наихудшую производительность. Тогда можно найти альтернативы, которые будут работать с той же сложностью, но именно на ваших данных показывать лучшую производительность.
Оптимизация алгоритма
Если лучших вариантов нет, посмотрите на реализацию текущего алгоритма. Постарайтесь уменьшить количество итераций, проходов по коллекциям и массивам. То есть N-1 проход — это быстрее, чем N проходов, хотя в O-нотации получается одна и та же сложность.
Если в вашем сервисе используется сложная математика, которая занимает время, попытайтесь её упростить. Однажды мы искали точки на плоскости, ближайшие к заданной. Для формулы вычисления расстояния нам нужен был квадратный корень: Math.sqrt(dx**2 + dy**2). Но для поиска ближайшей точки достаточно было сравнивать квадраты расстояний: dx**2 + dy**2. Это даст тот же самый ответ, но при этом мы избавимся от медленного вычисления корня.
Посмотрите внимательно на свои сложные вычисления — возможно, у вас получится применить такой же подход.
Вынос инвариантов на уровень выше
Бывает, что на каждой итерации вы постоянно проверяете или вычисляете одно и то же выражение, которое в следующей итерации не изменится. Например:
items.forEach(i => doClear ? i.clear() : i.mark());
Вместо того чтобы вычислять его N раз, было бы неплохо вынести его из цикла и проверять или вычислять только один раз:
if (doClear) items.forEach(clear)
else items.forEach(mark);
Как вариант, в этом коде мы можем по условию подставлять нужную функцию-итератор в перебор массива через forEach:
const action = doClear ? clear : mark;
items.forEach(action);
Такой приём касается не только циклов и операций над массивами. Он также применим к методам и функциям. Вот изоморфный код, который вычисляет, видима ли domNode в указанной точке экрана:
const visibleAtPoint = function(x, y, domNode) {
if (canUseDom && document.elementsFromPoint) {
// ...
}
};
Чтобы работать на сервере, код проверяет, можно ли использовать DOM. А когда код выполняется в браузере, он проверяет, обладает ли браузер требуемым API. Такие проверки происходят при каждом вызове функции. Это ненужные затраты времени и на сервере при server-side rendering, и на клиенте, потому что браузер или обладает API, или нет, и это достаточно проверить один раз.
Способ это исправить — завести две реализации, по одной для каждого из вариантов, проверить один раз, обладает ли наша среда выполнения нужными свойствами, и в зависимости от этого подставить нужную реализацию. Внутри реализации уже не будет никаких if, никаких проверок условий. Она будет работать максимально быстро.
const visibleAtPoint =
(canUseDom && document.elementsFromPoint) ? fn1 : fn2;
Boolean short circuit
Сейчас, к сожалению, часто забывают про Boolean short circuit. Даже JavaScript умеет не вычислять до конца логические выражения, составленные из операторов и/или, если он может заранее предсказать результат, как в этом примере.
const isVisibleBottomLeft =
visibleAtPoint(left, bottom, element);
const isVisibleBottomRight =
visibleAtPoint(right, bottom, element);
return isVisibleBottomLeft && isVisibleBottomRight;
Код проверяет видимость нижней левой и нижней правой точки прямоугольника — диалога, модального окошка. Если нижний левый угол уже не виден, то нам не надо вычислять видимость нижнего правого угла. Но в такой записи мы всё равно сначала всё вычислим и потом подставим в логическое выражение. Это без нужды замедляет наш код, особенно если проверки идут очень часто.
Чтобы воспользоваться преимуществами Boolean short circuit, надо подставлять вычисления и вызовы функций в само выражение. Тогда не будет вычисляться то, что не нужно.
return visibleAtPoint(left, bottom, element) &&
visibleAtPoint(right, bottom, element);
Досрочный выход из цикла
Иногда мы уже знаем результат нашего выражения, и нам не обязательно совершать поиск по всему массиву, как в этом примере:
let negativeValueFound = false;
for (let i = 0; i < values.length; i++) {
if (value[i] < 0) negativeValueFound = true;
}
Мы ищем, есть ли в массиве отрицательные значения. Но автор кода забыл, что, возможно, отрицательным будет самый первый элемент и нет смысла перебирать все оставшиеся, когда мы уже знаем ответ. Поэтому смотрите на свой код. После того, как вы узнаете ответ, нет необходимости продолжать итерацию по массивам и коллекциям. Надо досрочно возвращать результат:
let negativeValueFound = false;
for (let i = 0; i < values.length; i++) {
if (value[i] < 0) {negativeValueFound = true; break;}
}
Такими методами, досрочно завершающими перебор, у массива являются find, findIndex, every и some. Есть проблема с методом reduce: он продолжает перебор массива до конца. В этом случае можно использовать библиотечные методы, например, Lodash предоставляет метод
— аналог reduce, но с возможностью досрочного выхода.
Предвычисление
Иногда некоторые константы можно посчитать заранее. Например, мы обрабатываем все точки изображения и вычисляем кубический корень из компонент RGB каждой точки:
const result = Math.pow(r, 1/3);
Очень часто при работе с изображениями нужны сложные и затратные математические вычисления. Скорость вычислений не очень большая: на моей машине получается примерно 7 млн операций в секунду. Другими словами, за секунду мы успеем обработать примерно два мегапикселя картинки. Это маловато для современных машин.
Мы можем заметить, что константа ⅓ вычисляется каждый раз, и запомнить её. При этом скорость работы увеличится до 10 млн операций в секунду.
const N = 1/3;
const result = Math.pow(r, N);
Но этого всё ещё недостаточно. Обратите внимание, что чаще всего компоненты R, G и B представляют собой байты. То есть каждый из них принимает всего лишь 256 значений. Соответственно, наш кубический корень, как и результат любой формулы над байтом, тоже может принимать только 256 значений.
Мы можем записать заранее все значения формулы, какой бы сложной она ни была, а в рантайме всего лишь выбирать нужное значение из массива:
const result = CUBE_ROOTS[r];
Мы получаем примерно десятикратное ускорение по сравнению с первоначальным кодом — точные результаты
немного отличаться. Чем сложнее формула, тем большее ускорение мы можем получить.
Такой приём называется lookup table (LUT): мы записываем заранее вычисленные значения в табличку и ценой дополнительной памяти получаем дополнительную скорость.
Для языков/фреймворков, в которых нет ленивых вычислений и приёма copy-on-write
Shortcut fusion
Это интересная концепция, которая полностью отсутствует в JavaScript. Предположим, у вас есть необходимость в целой цепочке выражений над массивом: array.map().filter().reduce(). JavaScript будет делать всё это последовательно. Сначала выполнит map, построит промежуточный массив. Потом выполнит filter, построит второй промежуточный массив и в конце выполнит reduce над всеми элементами промежуточного массива. Получается три прохода, которые мы могли бы объединить в один, сделав shortcut fusion: написав один сложный array.reduce() с кодом из наших map, filter и reduce.
Бонусы shortcut fusion: промежуточные структуры данных не создаются и не потребляют память, мы не копируем в них содержимое предыдущего промежуточного массива, а число итераций уменьшается до одной. В мощных языках это делает под капотом сам компилятор. Мы в JavaScript вынуждены делать это вручную.
Ленивое вычисление
Оно тоже отсутствует в JavaScript. Иногда из всего массива нам требуется только пять первых элементов: arr.map().slice(0, 5). Или первый элемент, который удовлетворяет какому-нибудь условию: arr.map().filter(Boolean)[0]. Подобные вещи в JS выполняются неэффективно: сначала мы делаем все операции над массивом целиком, а потом оставляем только нужные элементы.
В следующем примере надо вычислить первые пять квадратных корней из нечётных элементов массива. Если мы запишем такую конструкцию в лоб, используя filter и map, то сложность реализации будет O(N):
array
.filter(n => n % 2)
.map(n => Math.sqrt(n))
.slice(0, 5);
Нам на помощь может прийти библиотека Lodash. В ней это же вычисление записывается очень похоже, но имеет сложность,
:
_(array)
.filter(n => n % 2)
.map(n => Math.sqrt(n))
.take(5).value();
Lodash под капотом использует и shortcut fusion, и ленивое вычисление, находя только первые пять элементов. Неважно, какова длина массива: как только мы найдём первые пять элементов, Lodash прекратит вычисления.
Аналогичные возможности, помимо Lodash, реализованы и в других библиотеках, например, Immutable и Ramda. Используйте их, когда у вас появляются такие цепочки вычислений.
Copy-on-write
В тех языках, где используется иммутабельность и копирование структур, есть приём: ленивое копирование содержимого этих структур в новый экземпляр, которое происходит, только когда мы действительно собираемся менять значение внутри.
const state = {
todos: [{todo: "Learn typescript", done: true}],
otherData: {}
};
Для нас это очень важно, потому что мы используем React, Redux и иммутабельный state. При этом создавать вручную следующий state из предыдущего очень неудобно:
const nextState = {...state,
todos: [...state.todos, {todo: "Try immer"}]};
Нам на помощь могут прийти библиотеки, которые за нас реализовали паттерн copy-on-write, например, библиотека
.
const nextState = produce(state, draft => {
draft.todos.push({todo: "Try immer"});
});
Мы как будто получаем следующий экземпляр state и смело делаем его мутирование, то есть добавляем в него элементы, меняем значения полей, а Immer под капотом производит копирование всего остального и добавление новых данных.
Помимо Immer есть несколько библиотек, в которых реализованы похожие вещи. В библиотеке Immutable есть методы updateIn, которые явно работают с иммутабельными структурами. В библиотеке Ramda есть концепция, которая называется «линзы». Мы создаём линзу и указываем в ней путь внутри объекта, в котором надо сделать мутацию значения. Читайте документацию, используйте эти библиотеки, когда нужно работать с иммутабельным state и другими иммутабельными структурами.
Оверинжиниринг
Порой легко увлечься и усложнить код там, где на самом деле можно сделать намного проще. Например, если вам нужно сделать обратный порядок элементов в массиве, для этого не нужны дополнительные библиотеки или сложный код, как здесь:
array.map().reverse()
Есть классический цикл for, в котором мы можем задать обратное направление:
for (let i = len - 1; i >= 0; i--)
Предположим, вам необходимо сделать вычисление над частью массива:
array.slice(1).forEach()
Тогда, опять же, можно использовать for, задав в нём нужный диапазон индексов:
for (let i = 1; i < len; i++)
Бывает, что мы усложняем код, делая слишком сложную цепочку:
_.chain(data)
.map()
.compact()
.value()[0]
Мы можем упростить и ускорить её, заменив на один вызов _.find() и проделав операции из map() только с одним найденным элементом
Зависящие от железа
До сих пор неявно предполагалось, что всё, что мы пишем, выполняется на идеальных компьютерах со сверхбыстрыми процессорами и мгновенным доступом к памяти. В реальности это не так, что в некоторых горячих местах становится особенно заметно.
Разворачивание мелких циклов
Как вы, наверное, знаете, мелкие циклы выполняются неэффективно, потому что затраты на организацию цикла перевешивают затраты на выполнение самого кода в цикле. В компилируемых языках компилятор разворачивает подобные мелкие циклы за вас и подставляет скомпилированный код, но в JavaScript приходится делать это вручную.
[1, 2, 3].map(i => fn(i))
В горячем коде, если вы заметите мелкие циклы на несколько элементов с использованием for, map, forEach, лучше развернуть их вручную:
[fn(1), fn(2), fn(3)]
Предсказание ветвлений (Branch prediction)
Если процессор может предугадать в вашей проверке if или switch, куда дальше передастся выполнение, он заранее начнёт разбирать этот код и выполнит его быстрее.
Вот пример бенчмарка (это синтетический бенчмарк, в реальном коде я аналогичных примеров не встречал). Есть массив из 100 тысяч элементов, которые мы перебираем в цикле. Внутри цикла стоит if, и в зависимости от проверки мы обрабатываем ровно половину элементов, а половину — нет.
Но в первом случае массив отсортирован, и мы сначала обрабатываем 50% элементов, а потом 50% оставшихся не обрабатываем. А во втором случае элементы, которые нужно обработать, случайно перемешаны по всему массиву. Нужно обработать ровно столько же элементов, но Branch prediction при этом не работает.
Обработка такого неупорядоченного массива занимает в разы больше времени даже на современных машинах: 550 мс против 130 мс. То есть даже в JavaScript Branch prediction может оказать заметное влияние на вычисления.
Если вы управляете порядком данных — например, тем, с какой сортировкой они приходят с бэкенда, — обратите на это внимание. Этот приём может помочь вам ускорить код.
Доступ к памяти: направление итерации
Как вы знаете, доступ к памяти происходит не мгновенно — современные компьютеры используют кэширование и упреждающее чтение данных для ускорения процесса. Есть старый паттерн, который родился в шестом Internet Explorer при операциях над циклами и строками: итерация в обратном направлении тогда была самой быстрой. С тех пор паттерн очень часто повторяется в современном коде «для большей скорости».
let i = str.length; while (i--) str.charCodeAt(i);
Но, к сожалению, это уже давно не так. В современных браузерах направление вперёд обычно работает быстрее (в данном примере — 1,6 против 1,4 млн операций в секунду):
for (let i = 0; i < str.length; i++) str.charCodeAt(i);
Даже на относительно коротких строках из нескольких сотен символов мы можем легко заметить разницу в скорости во всех современных браузерах и в Node.js.
Пример из библиотеки хэширования строк
.
Так что не используйте этот паттерн, пишите простой цикл for и итерацию в прямом направлении. Таким образом железо сможет наиболее оптимально дать вам следующие данные, которые вы собираетесь читать или менять.
Доступ к памяти: [i][j] vs [j][i]
Предположим, у вас есть двумерные структуры. Например, вы прочитали записи таблицы или двумерный массив в память. Тогда имеет смысл расположить последовательно в памяти строки или колонки, по которым вы будете итерироваться.
Если вы обрабатываете массив построчно, элементы одной строки должны лежать в памяти рядом. Если вы сканируете таблицу по колонке — например, ищете запись по индексу в таблице базы данных, — именно эта колонка должна лежать в соседних ячейках памяти. Такой приём может дать заметный прирост скорости (1 2).
Для языков со сборкой мусора
Эта группа оптимизаций подходит для языков, в которых есть garbage collection и автоматическое управление памятью: JavaScript, C#, Java и некоторых других.
Мутабельность
Первая проблема — плохая поддержка иммутабельности. Иммутабельность объектов означает генерацию новых объектов, иногда с довольно большой скоростью. А старые объекты должны собираться через garbage collector. В горячем коде это может очень сильно влиять на скорость работы. Именно затраты на сборку мусора могут превышать затраты на работу вашего кода. И если вы видите, что в горячем коде есть сильное потребление памяти, постарайтесь использовать мутабельность: убрать spread, убрать клонирование объектов и мутировать существующие объекты.
Иногда это можно сделать довольно безболезненно.
const obj = createNewObj();
return {...obj, prop: value};
Например, в таком горячем участке кода мы создаём свежий объект с нуля. Это гарантированно уникальный объект, никто на него не ссылается. И тут же, в следующей строке, мы его клонируем в новый объект. Здесь совершенно зря происходит и клонирование объекта, и создание мусора. Этот кусочек кода можно переписать вот так, будет намного быстрее:
const obj = createNewObj();
obj.prop = value;
return obj;
Но это, повторюсь, только в горячем коде. В остальных местах такое решение усложнит код, сделает его менее читаемым и менее сопровождаемым.
Zero memory allocation или GC-free
Так называются алгоритмы с низким или нулевым потреблением памяти. Общий приём в подобных алгоритмах — использование пула объектов. Мы один раз создаём N объектов заданного типа, и те, кто ими пользуются, мутируют их, как им надо, а потом возвращают обратно в пул. Таким образом, нет потребления новой памяти.
Предположим, есть возвращаемый объект, который нужен один раз «на выброс» — то есть надо сделать однократно какую-то операцию, и больше нас объект не интересует, мы нигде не сохраняем ссылки на него. Тогда можно использовать синглтон. Этот паттерн называется flyweight object.
Вот пример из фреймворка ExtJS:
Ext.fly(id)
Use this to make one-time references to DOM elements which are not going to be accessed again either by application code, or by Ext’s classes.
Это довольно частый паттерн работы с DOM: мы получаем по идентификатору DOM-элемент, на нём проверяем или меняем CSS-класс, стили, атрибуты и выбрасываем его, так как он нам больше не нужен. В этом случае подходит именно flyweight object.
В других языках самое распространённое применение этого алгоритма — в библиотеках логирования, которые могут вызываться очень часто, поэтому нагрузка на память становится важной. Вот ссылки на клиенты логирования в языках Go и Java:
В документации хорошо описано, что именно и как именно делалось. Можно найти и проанализировать пулл-реквест, в котором снижалось потребление памяти.
Специфичные для JavaScript
Эта группа оптимизаций наиболее близка именно к JS и мало применима в других языках.
Антипаттерн: накопление строк в массиве
Ещё один антипаттерн со времён шестого Internet Explorer — если нужно накопить длинную строку из кусочков, некоторые разработчики до сих пор сначала собирают эти строки в массив, чтобы потом вызвать join:
[string1, string2, … stringN].join('')
К сожалению, это работало быстро только в шестом Internet Explorer. С тех пор стало гораздо быстрее суммировать строки «в лоб»:
string1 + string2 + … + stringN
Потому что в браузерах для такого представления строки есть специальный класс
, «конкатенированная строка». Он позволяет осуществить сложение строк за константное время, то есть сохраняет внутри только две ссылки на две суммируемых строки и не занимается физическим копированием байтиков из одного места в другое. Так что суммируйте строки как есть, не используйте для этого массив и join.
Антипаттерн: Lodash _.defaults
Когда у нас есть объект, в котором мы хотим завести дефолтное значение, для этого мы часто используем функцию _.defaults из Lodash или её аналоги. В этом случае в сами дефолтные значения легко записать результат сложных вычислений, которые занимают длительное время.
_.defaults(button, {
size: getDefaultButtonSize(window),
text: getDefaultButtonText()
});
В этом примере кода, когда приходят пропсы для кнопки, мы хотим, чтобы у них были дефолтные размеры и текст. Вычисления дефолтных размеров и текста мы проделываем для дефолтных полей, даже если в пришедших к нам свойствах кнопки уже есть поля size и text. То есть мы сначала вычислим объект дефолтных значений и потом решим, будем ли его использовать.
Быстрый некрасивый фикс: сначала проверять, нужны ли нам дефолты в данном поле, и только тогда выполнять тяжёлые вычисления:
if (button.size === undefined)
button.size = getDefaultButtonSize(window);
if (button.text === undefined)
button.text = getDefaultButtonText();
Но, конечно, такой код получается некрасивым. Немного красивее будет написать с использованием геттеров:
_.defaults(button,{
get size() {return getDefaultButtonSize(window)},
get text() {return getDefaultButtonText()}
});
Вариант ещё лучше: если вы понимаете, что у вас в горячем коде часто генерируются пропсы, в которых используется дефолтное значение, сделайте правильную генерацию этих пропсов, чтобы внутри кода, который их генерирует, все они сразу получали дефолтные значения. Постарайтесь сделать этот код красивым и быстрым — это вполне достижимый результат.
Idle Until Urgent
Часто мы в конструкторе объекта инициализируем все поля, которые нам, возможно, потребуются только после некоторых действий пользователя или не потребуются вообще — как в этом примере, взятом из
Филипа Уолтона:
constructor() {
this.formatter = new Intl.DateTimeFormat(…);
}
handleUserClick() {
const formatter = this.formatter;
this.clickTime = formatter.format(new Date());
}
Мы создаём поле formatter для форматирования даты и времени, и это создание длится очень долго. Но formatter, возможно, будет использован только через большой промежуток времени, когда пользователь на что-то нажмёт.
Мы можем погрузить медленное создание объекта formatter в обёртку, которая выполнит код создания объекта, когда браузер будет свободен и пользователь не будет ничего делать или когда нам явно потребуется этот formatter:
constructor() {
this.formatter = new IdleValue(
() => new Intl.DateTimeFormat(…));
}
handleUserClick() {
const formatter = this.formatter.getValue();
this.clickTime = formatter.format(new Date());
}
IdleValue — класс, реализующий ленивую инициализацию. Описан в вышеупомянутой статье и в библиотеке
.
Так мы сэкономим время на критическом этапе загрузки страницы и не будем замедлять создание объектов.
Даунгрейд кода: ES6 → ES5
Не секрет, что до сих пор многие фичи ES6 и более новые работают медленнее, чем их аналоги из ES5. В горячем коде попробуйте заменить их на ES5-код и, возможно, получите ускорение.
- Итераторы, for-of, map/reduce/forEach заменяйте на for
- Object.keys, Object.entries заменяйте на for-in
- Старайтесь не использовать rest и spread
Пример — хуки в React рекомендуют деструктурировать вот таким образом:
const [x, setX] = useState(0);
Если у нас нет поддержки деструктурирования, то мы транспилируем этот код в нативный ES5-вариант, в котором мы сначала получаем массив, а потом читаем из него элементы по двум индексам:
const state = useState(0),
x = state[0],
setX = state[1];
Как ни странно, когда появилось нативное деструктурирование, этот ES5-вариант работал в разы быстрее нативного, потому что под капотом деструктурирование двух элементов массива реализовано через создание итератора, два вызова next, две проверки на достижение конца итерации, и всё это ещё завёрнуто в try-catch. Такая нативная реализация деструктурирования в принципе не может работать быстрее, чем просто доступ к двум элементам массива.
Как мы видим по бенчмарку, со временем деструктурирование в Chrome было немного оптимизировано в ущерб транспилированному варианту. А в других популярных браузерах деструктурирование до сих пор работает медленно:
Так что в горячем коде, пожалуйста, обратите внимание: даже деструктурирование может привести к замедлению.
Примеры из код-ревью
На закуску — примеры реального кода, увиденные во время код-ревью.
- Как вы думаете, что делает такой код? Что хотел сделать его автор?
Boolean(_.compact(array).length)
ОтветКод проверяет, есть ли хоть один не пустой элемент в массиве. Вы наверняка догадались, что можно использовать приём, о котором я уже рассказал: выходить из итерации, как только найден ответ, а не перебирать все элементы до конца:
array.some(x => Boolean(x))
- А такой?
array.sort((a, b) => b - a)[0]
ОтветАвтор хотел наиболее компактно, сэкономив лишние буквы, найти максимальный элемент в массиве. Но как вы понимаете, стоимость такого поиска намного больше, чем хотелось бы. Зная это, мы можем использовать специальный метод из библиотеки или написать свой, утилитный:
_.max(array)
- Представьте такой угар по иммутабельности:
array.reduce((acc, value) => [...acc, someFn(value)], [])
Даже тот массив, который создан внутри reduce, мы всё равно каждый раз клонируем и тем самым занимаем лишние время и память. Используя мутабельность, можно было бы сильно ускорить выполнение:array.reduce((acc, value) => { acc.push(someFn(value)); return acc; }, [])
- Последний пример кода:
arr.filter(predicate).length
Здесь остановлюсь немного подробнее. Мы хотим посчитать количество элементов, которые удовлетворяют условию (для которых predicate вернёт true). Если бы у нас был умный компилятор, или использовался язык с ленивыми вычислениями, то он бы догадался, что нам от промежуточного массива после filter нужна только длина. В коде вычисления этого промежуточного массива он оставил бы только увеличение длины массива при выполнении условия predicate. А само создание массива и копирование его элементов — выбросил бы. Фактически он бы за нас написал конструкцию, в которой лишь инкрементируется длина массива.Но в JavaScript, к сожалению, мы должны сами видеть, что такой код выполняется неоптимально и оптимизировать его вручную, выполняя работу за компилятор:
arr.reduce((count, x) => predicate(x) ? count + 1 : count, 0)
Вместо заключения
Вот
на GitHub, где я собрал все упомянутые в тексте ссылки. Надеюсь, вы попробуете применить эти приёмы на практике и поделитесь опытом в комментариях. Также пишите, если уже пробовали что-то из списка или если у вас есть свои идеи, как ускорить работу кода на JavaScript, — обсудим. Спасибо за внимание. Всем быстрого кода!
I for all time emailed this web site post page to all
my associates, because if like to read it afterward
my contacts will too.
Good day I am so excited I found your webpage, I really found you by accident, while I was searching on Google for something else, Anyways I am here now and would
just like to say thanks for a remarkable post and a all round exciting blog (I also love the theme/design), I don’t have
time to read through it all at the minute but I have book-marked it and also included your RSS feeds, so when I
have time I will be back to read a lot more, Please do keep
up the awesome work.
I just couldn’t leave your web site before suggesting that I actually
loved the usual information an individual provide on your guests?
Is gonna be again ceaselessly in order to investigate cross-check new posts
I will right away grasp your rss as I can’t to find your e-mail subscription hyperlink or
e-newsletter service. Do you have any? Please permit me know so
that I may subscribe. Thanks.
Oh my goodness! Amazing article dude! Thanks, However I am going through issues with your
RSS. I don’t know why I am unable to join it. Is there
anybody else getting identical RSS issues? Anybody who
knows the answer can you kindly respond? Thanx!!
Pretty section of content. I just stumbled upon your
site and in accession capital to assert that I get in fact enjoyed account your blog posts.
Anyway I’ll be subscribing to your feeds and even I achievement you access consistently fast.
Hmm it looks like your site ate my first comment (it was extremely long) so I guess I’ll just sum it up what I wrote and say, I’m thoroughly enjoying your blog.
I too am an aspiring blog writer but I’m still new to everything.
Do you have any suggestions for newbie blog writers?
I’d genuinely appreciate it.
I like the valuable information you provide in your
articles. I’ll bookmark your blog and check again here regularly.
I’m quite sure I will learn many new stuff right here!
Best of luck for the next!
obviously like your website but you need to check the spelling on quite a few of your posts.
Many of them are rife with spelling issues and I find
it very troublesome to tell the reality nevertheless I’ll definitely come back again.
Nice blog right here! Also your site lots up fast!
What host are you the use of? Can I am getting your associate link in your host?
I wish my site loaded up as quickly as yours lol
Why visitors still make use of to read news papers when in this technological world all
is existing on net?
First of all I want to say awesome blog! I had a quick question in which I’d like to ask if
you don’t mind. I was interested to know how
you center yourself and clear your head before writing.
I have had trouble clearing my mind in getting my ideas out.
I do enjoy writing but it just seems like the first 10 to
15 minutes are wasted simply just trying to figure out how to begin. Any recommendations or hints?
Many thanks!
These are really wonderful ideas in about blogging. You have touched some nice points here.
Any way keep up wrinting.
Thank you for the good writeup. It in fact was a amusement account it.
Look advanced to far added agreeable from you!
By the way, how can we communicate?
Hi everyone, it’s my first visit at this site, and piece of
writing is really fruitful for me, keep up posting these types of posts.
It’s perfect time to make some plans for the long run and it’s time to be happy.
I have learn this publish and if I may just I wish to recommend you few interesting issues or advice.
Maybe you could write subsequent articles relating to this article.
I wish to learn more issues approximately it!
Aw, this was a very nice post. Spending some time and actual effort to
produce a very good article… but what can I say…
I put things off a lot and don’t seem to get anything done.
Pretty! This has been a really wonderful article.
Thanks for providing these details.
Hey there! This is my first visit to your blog! We are a
group of volunteers and starting a new project in a community in the same niche.
Your blog provided us beneficial information to work on. You have done
a wonderful job!
Thank you for some other fantastic article. The place else may just anybody get that kind of
information in such a perfect approach of writing?
I’ve a presentation next week, and I am at the look for such information.
fantastic put up, very informative. I’m wondering why the other specialists
of this sector do not realize this. You must continue your writing.
I am sure, you have a huge readers’ base already!
It’s very straightforward to find out any topic on net as compared to books, as I found this paragraph at
this web page.
Good answer back in return of this issue with firm arguments and describing
everything concerning that.
What’s up, yeah this piece of writing is actually good and I have learned lot of things from it regarding blogging.
thanks.
I visited many web sites but the audio feature for audio
songs existing at this web page is in fact superb.
Thank you, I have just been looking for info about this topic for ages and yours is the best I’ve
discovered till now. However, what about the conclusion? Are you positive about the source?
I’ve read some excellent stuff here. Certainly value bookmarking for revisiting.
I surprise how much effort you put to create the sort of magnificent informative site.
Please let me know if you’re looking for a article writer
for your weblog. You have some really great articles
and I believe I would be a good asset. If you ever want
to take some of the load off, I’d absolutely love to write some material for your blog in exchange for a
link back to mine. Please shoot me an email if interested.
Many thanks!
Thanks for your marvelous posting! I really enjoyed reading it, you are a great author.
I will make certain to bookmark your blog and will eventually come back down the road.
I want to encourage one to continue your great posts, have a nice holiday weekend!
взять беспроцентный займ
домус ремонт квартир
доделать ремонт квартиры
ремонт квартир белебей
мужской салон в уфе
кодировка от алкоголизма уфа
запой вывод на дому уфа
наркология вывод из запоя на дому
лечение наркозависимости недорого
1xbet рабочие промокоды
промокод на 1xbet видео
квартира перед ремонтом
ремонт квартир суды
ремонты квартир в киеве
аренда танк контейнеров
перевозка наливных грузов в танк контейнерах
промокод 1xbet