Кеширование медленных вычислений

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

В библиотеке Underscore.js есть метод, который для любой функции делает версию этой функции с кешированием — memoize.

Так, например, её можно применять для кеширования _.template().


var View = Backbone.View.extend({
  render: function () {
    var markup = _.template(this.template, this.model.toJSON());
    this.$el.html(markup);
  }
});

Вместо того, чтобы при каждом обновлении данных компилировать тектовый шаблон , он будет преобразован в функцию только при первом использовании. А все последующие вызовы будут использовать уже скомпилированный вариант.


var cachedTemplate = _.memoize(_template);
var View = Backbone.View.extend({
  render: function () {
    var markup = cachedTemplate(this.template, this.model.toJSON());
    this.$el.html(markup);
  }
});

Разница в производительности при рендеринге простенького шаблона и его кешированного варианта налицо.

Все скомпилированные шаблоны находятся в хранилище ключ-значение внутри замыкания _.memoize(). Ключом в нашем случае выступает текст шаблона, а значением — скомпилированная функция.

В качестве ключа может быть не только текстовая строка. Если первый параметр кешируемой функции не является строкой, то ключом станет то, что венёт его метод .toString() . В некоторых случаях это может оказаться неприемлемо (например, когда первым аргументом идёт объект). Чтобы избежать коллизий, в _.memoize() можно передать опциональную функцию, генерирующую ключ из аргументов.


var cachedTemplate = _.memoize(_.template, function (value) {
  return JSON.stringify(value);
});

Аналогичный приём, но без использования Underscore.js я применяю для кеширования асинхронных запросов.