Автор оригинала: 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' }]
Вот и это на сегодня. Я мог продолжать и показывать вам много разных примеров абстракций, но я позволю тебе играть с этими понятиями.
Вы можете другие статьи такие в моем блоге Отказ