Затварања, цурриед функције и цоол апстракције у ЈаваСцрипт-у

У овом чланку ћемо разговарати о затварању и функцијама са цурриед-ом и поиграћемо се са овим концептима како бисмо створили цоол апстракције. Желим да покажем идеју која стоји иза сваког концепта, али и да је учиним врло практичном помоћу примера и преправљеног кода како би била забавнија.

Затварања

Затварања су уобичајена тема у ЈаваСцрипт-у и то је она с којом ћемо започети. Према МДН-у:

Затварање је комбинација функције повезане у пакету (затворена) са референцама на њено окружење (лексичко окружење).

У основи, сваки пут када се креира функција, створи се и затварање и она даје приступ стању (променљиве, константе, функције итд.). Околна држава је позната као lexical environment.

Покажимо једноставан пример:

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

Шта то имамо овде?

  • Наша главна функција је позвана makeFunction
  • Константа именована nameдодељује се низом,'TK'
  • Дефиниција displayNameфункције (која само записује nameконстанту)
  • И на крају, makeFunctionвраћа displayNameфункцију

Ово је само дефиниција функције. Када позовемо makeFunction, створиће све у себи: константу и другу функцију, у овом случају.

Као што знамо, када се displayNameфункција креира, ствара се и затварање и чини функцију свесном свог окружења, у овом случају nameконстанте. То је разлог зашто можемо константа без прекидања ништа. Функција зна за лексичко окружење.console.logname

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

Сјајно! Ради како се очекивало. Повратна вредност makeFunctionфункције је функција коју чувамо у myFunctionконстанти. Када зовемо myFunction, приказује се 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 

myFunction је свестан аргумента који је прослеђен и да ли је то подразумевана или динамичка вредност.

Затварање осигурава да креирана функција није свесна само константи / променљивих, већ и других функција унутар функције.

Дакле, ово такође функционише:

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

Враћена функција зна за displayфункцију и може је позвати.

Једна моћна техника је употреба затварача за изградњу „приватних“ функција и променљивих.

Пре неколико месеци учио сам структуре података (опет!) И желео сам да применим сваку од њих. Али увек сам користио објектно оријентисани приступ. Као љубитељ функционалног програмирања, желео сам да изградим све структуре података следећи ФП принципе (чисте функције, непроменљивост, референтна транспарентност, итд.).

Прва структура података коју сам учио био је Стацк. Прилично је једноставно. Главни АПИ је:

  • push: додајте ставку на прво место стека
  • pop: уклоните прву ставку из гомиле
  • peek: узмите прву ставку из стека
  • isEmpty: проверите да ли је стек празан
  • size: добијте број предмета које стек има

Јасно смо могли створити једноставну функцију за сваку „методу“ и пренети јој податке о стеку. Тада би могао да користи / трансформише податке и врати их.

Али такође можемо створити стек са приватним подацима и изложити само АПИ методе. Урадимо то!

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, }; }; 

Будући да смо створили itemsстек унутар наше buildStackфункције, он је „приватан“. Може му се приступити само унутар функције. У том случају, само push, popи тако би се могло дирати податке. То је управо оно што тражимо.

А како да га користимо? Овако:

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 

Дакле, када се стек креира, све функције су свесне itemsподатака. Али изван функције, не можемо приступити овим подацима. То је приватно. Ми само модификујемо податке помоћу уграђеног АПИ-ја стека.

Цурри

„Каририрање је поступак узимања функције са више аргумената и претварања у низ функција од којих свака има само један аргумент.“

- Фронтенд интервју

Замислите да имате функцију са више аргумената: f(a, b, c). Коришћењем цурри-а постижемо функцију f(a)која враћа функцију g(b)која враћа функцију h(c).

У основи: f(a, b, c)->f(a) => g(b) => h(c)

Направимо једноставан пример који додаје два броја. Али прво, без карирања:

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

Сјајно! Супер једноставно! Овде имамо функцију са два аргумента. Да бисмо је трансформисали у карирану функцију, потребна нам је функција која прима xи враћа функцију која прима 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; 

Ове три различите цурри функције имају исто понашање: изградите низ функција са само једним аргументом.

Како можемо да га користимо?

add(10)(20); // 30 

У почетку може изгледати помало чудно, али иза тога постоји логика. add(10)враћа функцију. И ову функцију зовемо са 20вредношћу.

Ово је исто као:

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

А ово је занимљиво. Специјализоване функције можемо генерисати позивањем прве функције. Замислите да желимо incrementфункцију. Можемо га генерисати из наше addфункције предавањем 1као вредност.

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

Када сам примењивао Лази Ципресс, нпм библиотеку за бележење понашања корисника на страници обрасца и генерисање Ципресс кода за тестирање, желео сам да изградим функцију за генерисање овог низа input[data-testid="123"]. Дакле, имао сам елемент ( input), атрибут ( data-testid) и вредност ( 123). Интерполацијом овај низ у ЈаваСцрипт би изгледало овако: ${element}[${attribute}="${value}"].

Моја прва примена била је примање ове три вредности као параметара и враћање интерполираног низа изнад:

const buildSelector = (element, attribute, value) => `${element}[${attribute}="${value}"]`; buildSelector('input', 'data-testid', 123); // input[data-testid="123"] 

И било је сјајно. Постигао сам оно што сам тражио.

Али у исто време, желео сам да изградим идиоматскију функцију. Нешто где бих могао да напишем „Г ет елемент Кс са атрибутом И и вредношћу З “. Дакле, ако ову фразу поделимо у три корака:

  • узми елемент Кс “:get(x)
  • са атрибутом И “:withAttribute(y)
  • " и вредност З ":andValue(z)

Можемо трансформисати buildSelector(x, y, z)у get(x)withAttribute(y)andValue(z)помоћу концепта цурриинг.

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

Овде користимо другачију идеју: враћање објекта са функцијом као кључ-вредност. Онда можемо постићи ову синтаксу: get(x).withAttribute(y).andValue(z).

And for each returned object, we have the next function and argument.

Refactoring time! Remove the return statements:

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

I think it looks prettier. And here's how we use it:

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

The andValue function knows about the element and attribute values because it is aware of the lexical environment like with closures that we talked about before.

We can also implement functions using "partial currying" by separating the first argument from the rest for example.

After doing web development for a long time, I am really familiar with the event listener Web API. Here's how to use it:

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

I wanted to create an abstraction to build specialized event listeners and use them by passing the element and a callback handler.

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

This way I can create different specialized event listeners and use them as functions.

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

Уз све ове концепте, могао бих да креирам СКЛ упит користећи ЈаваСцрипт синтаксу. Желео сам да питам ЈСОН податке попут овог:

const json = { "users": [ { "id": 1, "name": "TK", "age": 25, "email": "[email protected]" }, { "id": 2, "name": "Kaio", "age": 11, "email": "[email protected]" }, { "id": 3, "name": "Daniel", "age": 28, "email": "[email protected]" } ] } 

Тако сам изградио једноставан механизам за руковање овом имплементацијом:

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) }); 

Овом имплементацијом можемо покренути мотор са ЈСОН подацима:

const select = startEngine(json); 

И користите га као СКЛ упит:

select(['id', 'name']) .from('users') .where('id', 1); result; // [{ id: 1, name: 'TK' }] 

То је то за данас. Могао бих вам наставити и даље показујући много различитих примера апстракција, али оставићу вам да се поиграте са овим концептима.

Можете и друге чланке попут овог на мом блогу.

Мој Твиттер и Гитхуб.

Ресурси

  • Изворни код блога
  • Затварања | МДН веб документи
  • Цурриинг | Фун Фун Функција
  • Научите реаговати стварањем апликације