Цель этой статьи - объяснить, как реализовать закрытие и мемоизацию в JavaScript. Когда я кодирую, я учусь лучше, и если вы похожи на меня, то эта статья вам наверняка понравится.

Теперь, прежде чем мы перейдем к теме, давайте подведем итог тому, что мы знаем о функциональном программировании - что такое замыкания и что мы на самом деле имеем в виду, когда используем термин мемоизация?

Функциональное программирование

Функциональное программирование - это декларативный тип стиля программирования. Это парадигма программирования, в которой мы пытаемся связать все в стиле чисто математических функций. Его основной упор делается на «что решать» в отличие от императивного стиля, где основной упор делается на «как решать».

Функциональное программирование основано на лямбда-исчислении. Он в основном работает по принципу, согласно которому функции рассматриваются в JavaScript как первоклассные граждане. Функции также могут быть функциями высшего порядка. Функция высшего порядка - это функция, которая принимает другие функции в качестве параметров и возвращает другую функцию.

Закрытие

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



Воспоминание

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

Продемонстрируем это на примере:

Давайте создадим простую функцию, которая добавляет 80 к числу и возвращает его.

function addTo80(num) {
   return num + 80;
}
console.log(addTo80(5));            // Computes and returns 85
console.log(addTo80(5));            // Computes and returns 85
console.log(addTo80(5));            // Computes and returns 85
Output:
85
85
85

Вышеупомянутая программа вычислит одну и ту же функцию 3 раза и вернет результат. Это очень простая функция, которая может не занимать много памяти и времени для выполнения. Но рассмотрим гораздо более сложную функцию, выполняющую одну и ту же задачу снова и снова. Например, поиск сотого элемента ряда Фибоначчи с помощью рекурсии. Вот где мемоизация может быть действительно эффективной и может сэкономить много времени на вычисления.

Улучшая приведенный выше пример, давайте создадим еще одну функцию memoizedAddto80 () и возьмем объект кеша, который хранит данные (в памяти) после того, как он вычислил значение для определенного аргумента. Давайте посмотрим, как это работает:

function addTo80(num) {
   return num + 80;
}
let cache = {};
function memoizedAddTo80(num) {
  if (num in cache) {
    return cache[num]; 
  } else {
    console.log('Calculating...');
    cache[num] = num + 80;
    return cache[num];
  }
}
// Computes and stores 85 in cache
console.log(memoizedAddTo80(5));
// Returns memoized data from memory
console.log(memoizedAddTo80(5));
// Returns memoized data from memory
console.log(memoizedAddTo80(5));
// Output:
Calculating...
85
85
85

Приведенный выше фрагмент кода вычислит результат один раз как 85, а затем сохранит его в объекте кеша, как показано ниже:

cache: {
   5: 85
}

Теперь, при следующем вызове memoizedAddTo80 (5), он будет искать его в памяти и возвращать напрямую, поскольку он уже был вычислен ранее. Эта операция действительно быстрая по сравнению с повторным выполнением той же операции.

А теперь подождите, вот пара вопросов, которые могли прийти вам в голову:

  1. Разве мы не загрязняем глобальное пространство имен, создавая глобальный кеш объектов, который можно изменить из любого места в приложении?
  2. Где используются закрытия?

Да, вы угадали! Мы действительно загрязняем глобальное пространство имен, и закрытия все еще не действуют.

Давайте проведем рефакторинг нашего кода, чтобы реализовать и решить обе проблемы, перечисленные выше:

function addTo80(num) {
   return num + 80;
}
function memoizedAddTo80() {
 let cache = {};
 return function(num) {
    if (num in cache) {
      return cache[num]; 
    } else {
      console.log('Calculating...');
      cache[num] = num + 80;
      return cache[num];
    } 
  }
}
const memoized = memoizedAddTo80();
// Computes and stores 85 in cache
console.log(memoized(5));
// Returns memoized data from memory
console.log(memoized(5));
// Returns memoized data from memory
console.log(memoized(5));
// Output:
Calculating...
85
85
85

Теперь на этот раз мы перемещаем наш глобальный кеш объектов внутрь функции memoizedAddTo80 (), чтобы предотвратить загрязнение глобального пространства имен. Проблема 1 решена. Теперь перейдем к проблеме 2.

Помните закрытия?

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

Изнутри функции memoizedAddTo80 () мы возвращаем другую функцию, которая отслеживает объект кеша, используемый в родительской функции. Это закрытие в действии. Внутренняя функция имеет доступ к состоянию свойства внешней функции (объекта кеша).

return function(num) {
    if (num in cache) {
      return cache[num]; 
    } else {
      console.log('Calculating...');
      cache[num] = num + 80;
      return cache[num];
    } 
  }

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

Вот демонстрация всего:



Вот и все. Надеюсь, эта статья оказалась для вас полезной. Спасибо за чтение. Не стесняйтесь оставлять свои комментарии, предложения и любые другие отзывы, которые могут быть полезны.

Больше контента на plainenglish.io