A closure egy programozási minta a JavaScriptben, amely lehetővé teszi, hogy egy külső lexical scope változóit egy egymásba ágyazott kódblokkon belül használd. A JavaScript átlátszóan kezeli a closure-öket, és gyakran anélkül használjuk őket, hogy tudnánk, mik is azok.
// Top-level declarations are global-scope
const dozen = 12;
{
// Braces create a new block-scope
// Referencing the outer variable is a closure.
const twoDozen = dozen * 2;
}
// Because of the block-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
A kapcsos zárójeleken ({}) kívül a függvények (és az osztályok) is új scope-okat hoznak létre, és képesek bezárni értékeket:
const dozen = 12;
// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
// This is declared inside the function scope, and uses the top-level scope.
// This works, and encloses the value 12.
const twoDozen = dozen * 2;
// This only uses the locally declared variable and the passed argument to the parameter `n`
return (twoDozen / 2) * n;
}
// Because of the function-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
Ahogy a twoDozen példák mutatják, az értékeket be lehet zárni egy egymásba ágyazott scope-ba (függvény, blokk stb.), de nem lehet kihúzni őket abból a kontextusból.
A legtöbb esetben a modern JavaScriptben szándékos, hogy egy érték ne szivárogjon ki egy külső scope-ba.
Egy módosítható változódeklaráció (például let vagy var) használatával megőrizhető valamennyi állapot:
let counter = 0;
// This function closure increments the counter's state in the outer lexical context.
// This way the counter can be shared between many calling contexts.
export function increment() {
counter += 1;
return counter;
}
increment();
// => 1
counter;
// => 1
A két ötletet ötvözve: ha egy értéket bezárunk, hogy megőrizzük az állapotot, és a bezárt értékek nem szivárognak ki, akkor privát értékeket hozhatunk létre.
A leggyakoribb módszer, hogy készítünk egy függvényt, amely egy olyan függvényt ad vissza, ami bezár magába valamennyi állapotot.
export function makeCounter() {
let counter = 0;
// This returns a new function that encloses the local variable counter
return function increment() {
counter += 1;
return counter;
};
}
// Counter did not leak
counter;
// => Uncaught ReferenceError: counter is not defined
// This creates a new counter.
// This assigns the increment function to the variable myFirstCounter.
const myFirstCounter = makeCounter();
typeof myFirstCounter;
// => function
myFirstCounter.name;
// => increment
myFirstCounter();
// => 1
myFirstCounter();
// => 2
// This creates new counter (with new, separate local state / enclosed counter variable)
const mySecondCounter = makeCounter();
mySecondCounter();
// => 1
// It does not affect the first counter.
myFirstCounter();
// => 3
Sok programozó nehéz fogalomnak tartja a closure-öket, és az, hogy egy függvény függvényt adjon vissza, nem gyakori, sőt néhány programozási nyelvben egyenesen lehetetlen. Ha több olvasnivalót szeretnél, az MDN closure-ökről szóló útmutatója elég átfogó.
A tervezőcég, ahol dolgozol, eddig elsősorban CSS-transzformációkkal épített weboldalakat. Némi megbeszélés után úgy döntesz, hogy elkezded használni a JavaScriptet, hogy bizonyos számításokat dinamikusan végezz el. A csapattársaid közül néhányan kevésbé jártasak JavaScriptben, ezért úgy döntesz, hogy closure-t használsz, hogy újrahasznosítható transzformációt készíts {x, y} koordinátapárokhoz.
Valósítsd meg a translate2d függvényt, amely closure-t használva visszaad egy függvényt, ami egy koordinátapár ismételhető 2D eltolását végzi.
A geometriában az eltolás azt jelenti, hogy pontokat, vektorokat vagy alakzatokat ugyanakkora távolsággal mozgatunk el egy adott irányba. Úgy is értelmezhető, mint egy konstans hozzáadása minden ponthoz.
const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]
Valósítsd meg a scale2d függvényt, amely closure-t használva visszaad egy függvényt, ami egy koordinátapár ismételhető 2D skálázását végzi.
A geometriában az egyenletes skálázás vektorok vagy alakzatok ugyanabba az irányba történő nagyítását vagy kicsinyítését jelenti. Úgy is értelmezhető, mint minden pont megszorzása egy konstanssal (skálázási tényezővel).
Ehhez a feladathoz feltételezd, hogy csak pozitív skálázási értékek vannak.
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
Kombinálj két transzformációs függvényt, hogy ismételhető transzformációt hozz létre. Ezt gyakran függvénykompozíciónak nevezik, ahol az első függvény f(x) eredménye lesz a második függvény g(x) bemenete.
const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
moveCoordinatesRight2Px,
doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]
Valósítsd meg a memoizeTransform függvényt. Ez egy függvényt vesz át memoizálásra, majd visszaad egy új függvényt, amely megjegyzi a kapott függvény bemeneteit, így az utolsó visszatérési érték „megjegyezhető”, és csak egyszer számítódik ki, ha ugyanazokkal az argumentumokkal hívják meg újra.
A memoizálást néha a dinamikus programozásban használják. Lehetővé teszi, hogy a költséges műveleteket csak egyszer hajtsák végre, mivel az eredményeiket megjegyzik. Megjegyzés: ebben a feladatban csak az utolsó eredményt jegyzi meg, ellentétben néhány dinamikus programozási megoldással, amelyek minden eredményt memoizálnak.
const tripleScale = scale2d(3, 3);
const memoizedScale = memoizeTransform(tripleScale);
memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments
memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) JavaScript nyelvet 37 fogalom159 feladat segítségével, valódi emberi mentorálással, mindez ingyen.