Рубрики
Без рубрики

Закрытия, каррические функции и крутые абстракции в JavaScript

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

Автор оригинала: FreeCodeCamp Community Member.

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

Закрытие

Закрытие – это общая тема в JavaScript, и это то, с которой мы начнем. По данным MDN:

В основном, каждый раз, когда создается функция, также создается закрытие, и она дает доступ к состоянию (переменные, константы, функции и т. Д.). Окружающее состояние известно как Лексическая среда Отказ

Давайте покажем простой пример:

function makeFunction() {
  const name = 'TK';
  function displayName() {
    console.log(name);
  }
  return displayName;
};

Что же мы имеем здесь?

  • Наша главная функция называется осуществление
  • Постоянная по имени Имя назначается со строкой, «ТК»
  • Определение DisplayName Функция (которая просто регистрирует имя Константа)
  • И, наконец, ManageFunction Возвращает DisplayName функция

Это просто определение функции. Когда мы называем ManageFunction Это создаст все в нем: константу и другую функцию в этом случае.

Как мы знаем, когда DisplayName Функция создается, замыкание также создается, и это делает функцию в курсе своей среды, в этом случае Имя постоянный. Вот почему мы можем console.log Имя постоян, не нарушая ничего. Функция знает о лексической среде.

const myFunction = makeFunction();
myFunction(); // TK

Большой! Это работает как ожидалось. Возвращаемое значение ManageFunction это функция, которую мы храним в Myфункция постоянный. Когда мы называем Myфункция это отображает TK Отказ

Мы также можем сделать его работать как функция стрелки:

const makeFunction = () => {
  const name = 'TK';
  return () => console.log(name);
};

Но что, если мы хотим передать имя и отобразить его? Простой! Используйте параметр:

const makeFunction = (name = 'TK') => {
  return () => console.log(name);
};

// Or as a one-liner
const makeFunction = (name = 'TK') => () => console.log(name);

Теперь мы можем играть с именем:

const myFunction = makeFunction();
myFunction(); // TK

const myFunction = makeFunction('Dan');
myFunction(); // Dan

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

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

Так что это также работает:

const makeFunction = (name = 'TK') => {
  const display = () => console.log(name);
  return () => display();
};

const myFunction = makeFunction();
myFunction(); // TK

Возвращенная функция знает о Дисплей функция и умеет называть это.

Одной из мощных методик является использование закрытия для создания «частных» функций и переменных.

Меся месяцев назад я изучал структуры данных (снова!) И хотел реализовать каждый. Но я всегда использовал объектно-ориентированный подход. В качестве энтузиаста функционального программирования я хотел создать все структуры данных после принципов FP (чистые функции, неизменность, референтную прозрачность и т. Д.).

Первая структура данных, которую я учился, была стек. Это довольно просто. Основная API:

  • толчок : добавьте элемент на первое место стека
  • поп : Удалите первый элемент из стека
  • заглянуть : Получить первый предмет из стека
  • ushumpy : Убедитесь, что стек пуст
  • Размер : получите количество предметов, которые имеет стек

Мы могли бы четко создавать простую функцию для каждого «метода» и передавать ему данные стека. Затем он может использовать/преобразовывать данные и вернуть его.

Но мы также можем создать стек с частными данными и выставлять только методы API. Давай сделаем это!

const buildStack = () => {
  let items = [];

  const push = (item) => items = [item, ...items];
  const pop = () => items = items.slice(1);
  const peek = () => items[0];
  const isEmpty = () => !items.length;
  const size = () => items.length;

  return {
    push,
    pop,
    peek,
    isEmpty,
    size,
  };
};

Потому что мы создали Предметы стек внутри нашего BuildStack Функция, это «частное». Доступ к работе только в функции. В этом случае только толчок , поп И так можно прикоснуться к данным. Это именно то, что мы ищем.

И как мы его используем? Как это:

const stack = buildStack();

stack.isEmpty(); // true

stack.push(1); // [1]
stack.push(2); // [2, 1]
stack.push(3); // [3, 2, 1]
stack.push(4); // [4, 3, 2, 1]
stack.push(5); // [5, 4, 3, 2, 1]

stack.peek(); // 5
stack.size(); // 5
stack.isEmpty(); // false

stack.pop(); // [4, 3, 2, 1]
stack.pop(); // [3, 2, 1]
stack.pop(); // [2, 1]
stack.pop(); // [1]

stack.isEmpty(); // false
stack.peek(); // 1
stack.pop(); // []
stack.isEmpty(); // true
stack.size(); // 0

Итак, когда стек создан, все функции знают о Предметы данные. Но вне функции мы не можем получить доступ к этим данным. Это личное. Мы просто изменим данные, используя встроенный API стека.

Карри

Поэтому представьте, что у вас есть функция с несколькими аргументами: F (A, B, C) Отказ Используя карри, мы достигаем функции f (а) Это возвращает функцию g (b) Это возвращает функцию h (c) Отказ

В основном: F (A, B, C) -> f (a) => g (b) => h (c)

Давайте построим простой пример, который добавляет два числа. Но во-первых, без карики:

const add = (x, y) => x + y;
add(1, 2); // 3

Большой! Супер просто! Здесь у нас есть функция с двумя аргументами. Чтобы преобразовать его в карриную функцию, нам нужна функция, которая получает х и возвращает функцию, которая получает y и возвращает сумму обоих значений.

const add = (x) => {
  function addY(y) {
    return x + y;
  }

  return addY;
};

Мы можем рефакторировать Addy в анонимную функцию стрелки:

const add = (x) => {
  return (y) => {
    return x + y;
  }
};

Или упростить его, создавая функции со стрелками на одну лайнер:

const add = (x) => (y) => x + y;

Эти три различных критерия Curried имеют одинаковое поведение: создайте последовательность функций только с одним аргументом.

Как мы можем использовать это?

add(10)(20); // 30

Сначала он может выглядеть немного странно, но за ним есть логика. Добавить (10) Возвращает функцию. И мы называем эту функцию с 20 значение.

Это так же, как:

const addTen = add(10);
addTen(20); // 30

И это интересно. Мы можем генерировать специализированные функции, вызвав первую функцию. Представь, мы хотим увеличение функция. Мы можем создать его из нашего Добавить Функция, проходя 1 как значение.

const increment = add(1);
increment(9); // 10

Когда я реализовал Ленивый кипарис , библиотека NPM для записи поведения пользователя на странице формы и генерировать код тестирования кипариса, я хотел создать функцию для создания этой строки Ввод [data-testid = "123"] Отказ Поэтому у меня был элемент ( вход ), атрибут ( data-testid ) и значение ( 123 ). Интерполяция этой строки в JavaScript будет выглядеть так: $ {Element} [$ {attribute} = "$ {значение}"] Отказ

Моя первая реализация состояла в том, чтобы получить эти три значения в качестве параметров и возвращать интерполированную строку выше:

const buildSelector = (element, attribute, value) =>
  `${element}[${attribute}="${value}"]`;

buildSelector('input', 'data-testid', 123); // input[data-testid="123"]

И это было здорово. Я добился того, что я искал.

Но в то же время я хотел построить более идиоматическую функцию. Что-то, где я мог написать «g et элемент x с атрибутом y и значение z ». Поэтому, если мы сломаем эту фразу на три шага:

  • Получите элемент x “: Получить (х)
  • « с атрибутом y »: сattribute (y)
  • и ценность z “: andvalue (z)

Мы можем преобразовать Buildselector (X, Y, Z) в Получить (х)с атрибутом (y)andvalue (z) Используя концепцию Carrying.

const get = (element) => {
  return {
    withAttribute: (attribute) => {
      return {
        andValue: (value) => `${element}[${attribute}="${value}"]`,
      }
    }
  };
};

Здесь мы используем другую идею: возвращение объекта с функцией в качестве значения ключа. Тогда мы можем достичь этого синтаксиса: Get (x) .withattribute (y) .andvalue (z) Отказ

А для каждого возвращенного объекта у нас есть следующая функция и аргумент.

Время рефакторинга! Удалить Возвращение заявления:

const get = (element) => ({
  withAttribute: (attribute) => ({
    andValue: (value) => `${element}[${attribute}="${value}"]`,
  }),
});

Я думаю, что это выглядит красивее. И вот как мы его используем:

const selector = get('input')
  .withAttribute('data-testid')
  .andValue(123);

selector; // input[data-testid="123"]

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

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

После долгое время выполняя веб-разработки, я действительно знаком с Слушатель событий Веб-API Отказ Вот как это использовать:

const log = () => console.log('clicked');
button.addEventListener('click', log);

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

const buildEventListener = (event) => (element, handler) => element.addEventListener(event, handler);

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

const onClick = buildEventListener('click');
onClick(button, log);

const onHover = buildEventListener('hover');
onHover(link, log);

Со всеми этими понятиями я мог создать SQL-запрос, используя синтаксис JavaScript. Я хотел запросить данные JSON, как это:

const json = {
  "users": [
    {
      "id": 1,
      "name": "TK",
      "age": 25,
      "email": "tk@mail.com"
    },
    {
      "id": 2,
      "name": "Kaio",
      "age": 11,
      "email": "kaio@mail.com"
    },
    {
      "id": 3,
      "name": "Daniel",
      "age": 28,
      "email": "dani@mail.com"
    }
  ]
}

Поэтому я построил простой двигатель для обработки этой реализации:

const startEngine = (json) => (attributes) => ({ from: from(json, attributes) });

const buildAttributes = (node) => (acc, attribute) => ({ ...acc, [attribute]: node[attribute] });

const executeQuery = (attributes, attribute, value) => (resultList, node) =>
  node[attribute] === value
    ? [...resultList, attributes.reduce(buildAttributes(node), {})]
    : resultList;

const where = (json, attributes) => (attribute, value) =>
  json
    .reduce(executeQuery(attributes, attribute, value), []);

const from = (json, attributes) => (node) => ({ where: where(json[node], attributes) });

С этой реализацией мы можем запустить двигатель с данными JSON:

const select = startEngine(json);

И использовать его как SQL-запрос:

select(['id', 'name'])
  .from('users')
  .where('id', 1);

result; // [{ id: 1, name: 'TK' }]

Вот и это на сегодня. Я мог продолжать и показывать вам много разных примеров абстракций, но я позволю тебе играть с этими понятиями.

Вы можете другие статьи такие в моем блоге Отказ

Мой Twitter и Github Отказ

Ресурсы