Мемоизация в 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(   `<div>       <button>Get the square root of 256</button>       <div>{{ src: "/api/getTheSquareRootOf256", after: "click:button" }}</div>   </div>` )().response;

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

Так вот, проблема предыдущих версий в том, что постоянно ставился новый элемент на каждый запрос, даже если ответ от запроса одинаковый.

Специально для оптимизации вот этого процесса было введено дополнительное поле, которое имеет название memo.

const newDiv = compile(   `<div>       <button>Get the square root of 256</button>       <div>{{ src: "/api/getTheSquareRootOf256", memo:true, after: "click:button" }}</div>   </div>` )().response;

Значением оно принимает true или false. Работает только для объектов запросов, которые теоретически неоднократно отправляются.

Для наглядного отображения процесса была создана небольшая диаграмма:

1. Мемоизация в HMPL (источник)

1. Мемоизация в HMPL (источник)

Тут тоже фигурирует понятие кэша, которое уже было использовано ранее. Также, если мы берём 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, можно найти тут.


ссылка на оригинал статьи https://habr.com/ru/articles/847548/