JavaScript Currying: адказаныя таёмніцы — замкнутыя функцыі, частая аплякацыя, паўторны выкарыстоўванне
Дазвольце дазнацца, як керіінг ператварае функцыі JavaScript з колькіма аргументамі на павтарна выкарыстоўваныя ланцюгі з адним аргументам, якое ў розніцы гэта з частым прызначэнням і калі можна яго не выкарыстоўваць.
Якщо вы постаўляеце той самы первы аргумент функцыі знова і знова, за кожны вызыв вам платзецца невялікі «падатак на павтарэнне». Куррыраванне дазволяе зафіксаваць частку вхідных дадзенняў функцыі адразу і паўтарна выкарыстоваць ўсё тое ж рэзультат. Чытаючы далей, вы дазнаецеся, што такое куррыраванне, якія закрыцція яго падтрымляюць, чым яно адрозніцаеся ад частковага застосоўвання і калі яно даследжва павышае якосьц коду.
Што значыць куррыраванне
Куррыраванне ператварае функцыю, якая прыймае калькі аргументаў, у серыю функцый, кожная з якіх прыймае роўна аднаго. Звычны вызыв прыносіць усе дадзеныя адразу:
f(a, b, c)
У форме куррыравання за кожны вызыв прыносіцца адна значэнне, а кожны вызыв вяртае наступную функцыю ў ланцугу:
f(a)(b)(c)
Кожная проміжная функцыя пам’ятае значэнні з паканальных вызываў. Гэтае пам’яткаванне здзейснюецца за дапамогою закрыццяў, пра якія пісана нижэй.
Канвертаванне звычайной функцыі
Пачнімо з простай функцыі дадавання двух аргументаў:
function sum(a, b) {
return a + b;
}
sum(2, 3); // 5
Маленькій дапаможнай функцыі можна выкарыстоўваць для абгорткі будзь-яй функцыі з двума параметрамі: зовнішняя функцыя прымеўа першую значэнне, вяртае функцыю, якая прымеўа другую, і толькі пасля чаго вызывае асалодную функцыю з обомі параметрамі:
function curry(fn) {
return function (a) {
return function (b) {
return fn(a, b);
};
};
}
const curriedSum = curry(sum);
curriedSum(2)(3); // 5
Што варта зазначыць:
curryўжоўця вышэйшага порядку: яе вхід і выход — гэта функцыі.- Кожная функцыя, яку ёна вяртае, чакае толькі адну значэнне.
- Внутраняя функцыя все ўсё можа чытаць
aпасля таго, як зовнішняя функцыя вярнулася, таму што яна «закрыта» над ёю.
Гэты дапаможнік обрабоўвае толькі ровна два параметры. Універсальныя версіі пераказваюць fn.length і продовжаюць збіраць аргументы, пакуль не будзе достатнько, але ідея застаецца тая ж.
Практычны прыклад: спецыялізаваныя логгеры
Арыметыка маскіруе сэнс, таму рассмотрзім логгер, який прымеўа рэвень серьёзнасці і паведамленне:
function log(level, message) {
console.log(`[${level}] ${message}`);
}
Месцы вызоў быстра начынаюць павтараць той самы рэжым:
log("ERROR", "Something broke");
log("ERROR", "Invalid token");
log("ERROR", "API failed");
Куріранты-логгеры дазваляюць стварыць заздалегідь настроеныя логгеры толькі раз:
function log(level) {
return function (message) {
console.log(`[${level}] ${message}`);
};
}
const errorLog = log("ERROR");
const infoLog = log("INFO");
errorLog("Something broke");
errorLog("Invalid token");
infoLog("User logged in");
Рэжым зафіксаваны, калі ствараюцца errorLog і infoLog. Месцы вызоў стаюць корачэй, намеры чытваюцца легка, а спецыялізаваныя функцыі можна экспортаваць або інжектаваць ў іншыя месца. Гэта поўнанне перадачы і выразнасці — галоўная прычына викорыстання курірантаў.
Частковая прыменення
Частковая прыменення значыць падзаполненне дзеякіх аргументаў існуючай функцыі, каб отрымаць новую функцыю, не зменяючы форму выначэння адной чакловай. JavaScript падтрымляе гэта натыўна за дапамогою bind:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
double(5); // 10
Першы ўзлог bind задае значэнне this, якое функцыя multiply ігнаруе, таму яго значэнне null. Функцыя multiply все рава прыймае два аргументы; double — это проста варіянт, у якой першы аргумент задаецца заздалегідь.
Курріюванне проты частковага застосавання
У обох падходаў адна мета, але разныя механізмы:
- Курріюванне перакладае функцыю у ланцуг функцый, якія прыймаюць по аднаму аргументу.
- Частковае застосавання заповнюе дзеякія аргументы працэсе выконання, залишаючы структуру функцыі без змян.
Кожны крок у ланцузе, створаным праз курріюванне, фактычна ёсць частковым застосаванням з аднам аргументам, але для частковага застосавання ніколи не трэба курріювання.
Безканальнае курріюванне
Ігровая варіянт працюе так, што продовжае приймать значэння, пакуль ланцуг не будзе завершаны порожнім вызывам:
let infiniteSum = function (a) {
return function (b) {
if (b !== undefined) return infiniteSum(a + b);
return a;
};
};
infiniteSum(1)(2)(3)(4)(); // 10
Як гэта працюе:
- Кожны выклік з значэнням дадае яго да сумы, яка ўтвараецца, і вяртае новую функцыю, яка несе гэтую суму.
- Пасляльні выклік
()заставляеbстацьundefined, таму вяртаецца сама сума. - Закрытыя функцыі перадають суму з аднаго крока на іншы.
Явная пераконтроўка b !== undefined мае важлівэ значэння. Тест на істыннасць, такі як if (b), спрацюе з 0 як з канцэном, таму infiniteSum(1)(0)(2)() зупініцца на нуле, а не вярнуць 3. Гэты патэран є чырлёгам для трэнавання закрытых функцый і частым пытаннем у спэкаваннях, але лёгкая да забывчыны пасляльні выклік робіць яго пасяродным варыянтом для прыёмных API.
Закрытыя функцыі дапамагаюць усём працаваць
Ось самы мінімальны куріраны аддытар:
function add(a) {
return function (b) {
return a + b;
};
}
add(3)(4); // 7
add(3) вяртае внутранюю функцыю і завершаеца, пры тым як гэта внутраняя функцыя все ўсё можа чытаць значэнне a. Гэта і є клозура: функцыя зберагае доступ да зменных з тэрыторіі, дзе яна была задана, нават пасля таго, як зовнішняя функцыя вярнулася. Паколькі зафіксаванае значэнне застаецца, можна зберагчы і павтарна выкарыстоўваць часткова прызначаную функцыю:
const add3 = add(3);
add3(4); // 7
add3(10); // 13
Ёсць больш інфармацыі пра тое, як разв’язваюцься зафіксаваныя зменныя, у стацыі «Як ланцуг тэрыторій у JavaScript разв’язвае зменныя».
Калі яго выкарыстоўваць, а калі праігнораваць
Куррыз становіць сэнс тады, калі:
- тые ж самыя аргументы з’яўляюцца ў багацько месцах вызову
- гэта робіць код ясней, а не болей загадкавым
- вы хочаце маленькія, складальныя, павтарна выкарыстоўваныя функцыі
Адказваце на гэта толькі калі:
- простая функцыя, якай выконуецца разоў, не выгадае ад ёё разбівання
- вааша команда не знае гэтага падходу
- перадача вызваў у ланцошку пагоршвае чытаемасць
Узагальненне
Курірант запоўнюецца парадкам, у якім функцыя прымае інпуты — настройкі, такія як рэжым логавання чыста базовая URL, зафіксуюцца з самага пачатку, а даны для кожнага вызваў прыходзяць пазней. Клозуры абавязуюцца запамятоваць гэты парадак, bind дазволяе выконваць функцыю часткова, калі не трэба цэлага ланцошка, а галоўны пытанне завжды застаецца тым, чы не стане код вызваў простым у чытанні і перызнаўцы.
Спаднія матэрыялы
- Розумеўце закрыццяў у JavaScript і цыклу змаганняў — Дзеянне пра тое, як закрыццяў заходзяць зместовыя зменныя і як цыкл змаганняў арранжуе стак вызваў, мікразадачы і макразадачы за дапамогою практычных прыкладаў коду.
- Як на самай працэ праглядаецца зместовы ланцуг у JavaScript для знаходжэння зменных — Пасвячана механізму лексыкальнага зместовага ланцуга, які відпавядае за пошук зменных, прычынам, чырэз якія зместовы модуль var спрычыняе багі ў цыклах, і таму, як закрыццяў є натуральным наследнікам гэтага механізма.