Мемоизация в HMPL. DevBlog №1
В версии 2.1.3, помимо прочего, был введён новый функционал для улучшения производительности сайтов, использующих hmpl.js.
Мемоизация запроса — это один из отличнейших способов оптимизации в программировании. «Что это и как оно работает?» — на эти вопросы я постараюсь ответить в данной статье.
Кстати, все нововведения, связанные с языком шаблонов, вы можете найти в тематическом тг-канале.
Понятие мемоизации
Прежде чем перейти к рассмотрению конкретного функционала, для начала рассмотрим данное понятия в программировании вообще.
Мемоизация — сохранение результатов выполнения функций для предотвращения повторных вычислений. Это один из способов оптимизации, применяемый для увеличения скорости выполнения компьютерных программ.
Перед вызовом функции проверяется, вызывалась ли функция ранее:
если не вызывалась, то функция вызывается, и результат её выполнения сохраняется;
если вызывалась, то используется сохранённый результат.
Примером мемоизации в JavaScript может быть следующий код:
// До внедрения мемоизации
const reverseText = (string) =>{
const len = string.length;
let reversedText = new Array();
let j = 0;
for (let i = len - 1; i >= 0; i--){
reversedText[j] = string[i];
j++;
}
return reversedText.join('');
}
const a = reverseText("a123"); // Срабатывает цикл
const b = reverseText("a123"); // Срабатывает цикл
// После внедрения мемоизации
const memoizeFn = (fn) => {
const cache = {};
return (string) => {
if (string in cache) return cache[string];
return (cache[string] = fn(string));
};
};
const memoizedReverseText = memoizeFn(reverseText);
const a = memoizedReverseText("a123"); // Срабатывает цикл
const b = memoizedReverseText("a123"); // Не срабатывает цикл
Мы создаём обёртку (функция высшего порядка) над уже существующий функцией, которая добавляет некое состояние, обозначенное cache
. В кэше хранятся аргументы и, соответственно, значения, получаемые из функции. По ключу, если он равен входному аргументу, в объекте уже можно получить готовый результат, не переворачивая заново строку.
Это и есть та основа на которой строится вся оптимизация. Банально, мы не повторяем код заново, а лишь берём уже вычисленное значение.
Также, объект был назван кэшем неспроста. Кэш— это промежуточный буфер с быстрым доступом к нему, содержащий информацию, которая может быть запрошена с наибольшей вероятностью.
Тут уже можно вспомнить и память, которая располагается между процессором и оперативкой, но это уже явно немного другая история.
В целом, мемоизация повсеместно применяется в ПО, что делает её отличным способом по быстрому ускорить то или иное выполнение кода. Но у данного способа есть конечно и минусы.
Основным минусом, конечно, является лишнее выделение памяти на хранение результатов. Если функция выполняется один раз, то смысла мемоизировать её возвращаемые значения попросту нет.
Мемоизация в HMPL
Так как HMPL — это язык шаблонов для отображения пользовательского интерфейса с сервера на клиенте, то мемоизировать нужно будет http запросы. Соответственно, предполагаемым результатом будет сохранение HTML разметки. Вот пример того, как работает HMPL:
const newDiv = compile(
`
{{ src: "/api/getTheSquareRootOf256", after: "click:button" }}
`
)().response;
По слушателю событий на кнопке очевидно, что запрос на сервер может отправляться сколь угодно раз, но вот квадратный корень из 256 как был 16, так и будет, поэтому запрос, теоретически, будет приходить один и тот же.
Так вот, проблема предыдущих версий в том, что постоянно ставился новый элемент на каждый запрос, даже если ответ от запроса одинаковый.
Специально для оптимизации вот этого процесса было введено дополнительное поле, которое имеет название memo
.
const newDiv = compile(
`
{{ src: "/api/getTheSquareRootOf256", memo:true, after: "click:button" }}
`
)().response;
Значением оно принимает true
или false
. Работает только для объектов запросов, которые теоретически неоднократно отправляются.
Для наглядного отображения процесса была создана небольшая диаграмма:
Тут тоже фигурирует понятие кэша, которое уже было использовано ранее. Также, если мы берём HTTP запросы, то рассматриваются дополнительные понятия fresh
и stale
response, ревалидация и т.д. Это всё идёт из теории HTTP кэша. Более подробно этот момент рассматривается в mdn документации тут. Можно провести аналогию, что мемоизация в HMPL по логике сравнима с no-cache
значением режима кэширования.
Пример работы hmpl без мемоизации и с ней в DOM:
Без мемоизации:
С мемоизацией:
В ходе теста активно нажималась кнопка получения пользовательского интерфейса с сервера.
В одном случае, мы постоянно заменяем div
на новый, хотя ответ от сервера приходит один и тот же, в другом же, мы сохраняем этот же элемент, но только если ответы одинаковы.
Мемоизация для типов файлов с расширением HMPL
Также, мемоизация будет доступна не только для одного объекта запроса, но и для всех объектов, полученных из функции compile
с включённой опцией memo
:
const templateFn = hmpl.compile(
`{
{
"src":"/api/test"
}
}`,
{
memo: true,
}
);
const elementObj1 = templateFn();
const elementObj2 = templateFn();
Она никак не помешает другим объектам запроса, которые срабатывают только один раз.
Так как на функции compile основана работа hmpl-loader, то в скором времени будет добавлена опция, при которой можно будет включить мемоизацию для всех файлов с расширением .hmpl
:
module.exports = {
module: {
rules: [
{
test: /\.hmpl$/i,
use: [{ loader: "hmpl-loader", options: { memo: true } }],
},
],
},
};
Для этого уже открыт issue.
Всем большое спасибо за прочтение данной статьи!
P.S. Больше изменений, которые были внесены в новую версию 2.1.3, можно найти тут.