Замикання - це програмний прийом у JavaScript, який дозволяє використовувати змінні із зовнішньої лексичної області видимості всередині вкладеного блоку коду. JavaScript підтримує замикання прозоро, і ми часто користуємося ними, навіть не знаючи, що це таке.
// 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
Крім фігурних дужок {}, функції (і класи) також створюють нові області видимості та можуть замикати в собі значення:
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
Як показують приклади з twoDozen, значення можна замкнути у вкладеній області видимості (функції, блоці тощо), але не можна витягти з цього контексту.
У більшості випадків у сучасному JavaScript задумано так, щоб значення не витікало за межі області видимості.
Використання оголошення змінної, яку можна змінювати (як let чи var), дозволяє зберегти певний стан:
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
Поєднавши дві ідеї: замикання значення для збереження стану та те, що замкнуті значення не витікають назовні, можна створити приватні значення.
Найпоширеніший метод - створити функцію, яка повертає функцію, що замикає в собі певний стан.
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
Багато програмістів вважають замикання складною концепцією, а повернення функції з функції є незвичним, а в деяких мовах програмування навіть неможливим. Якщо потрібно більше матеріалу для читання, посібник MDN про замикання досить вичерпний.
Наша компанія з дизайну переважно працювала з CSS-трансформаціями, створюючи вебсторінки. Після деяких обговорень ми вирішили почати використовувати JavaScript, щоб виконувати частину обчислень динамічно. Деякі з наших колег мають менший досвід роботи з JavaScript, тож ми вирішуємо використати замикання функції, щоб створити багаторазову трансформацію для пар координат {x, y}.
Реалізуйте функцію translate2d, яка повертає функцію, що використовує замикання для багаторазового двовимірного перенесення пари координат.
У геометрії перенесення означає переміщення точок, векторів або фігур на однакову відстань в одному напрямку. Його можна тлумачити як додавання сталої до кожної точки.
const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]
Реалізуйте функцію scale2d, яка повертає функцію, що використовує замикання для багаторазового двовимірного масштабування пари координат.
У геометрії рівномірне масштабування означає збільшення або зменшення векторів чи фігур в одному напрямку. Його можна тлумачити як множення кожної точки на сталу (коефіцієнт масштабування).
Для цієї вправи вважаймо, що значення масштабу лише додатні.
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
Поєднайте дві функції трансформації, щоб виконувати багаторазову трансформацію. Це часто називають композицією функцій, де результат першої функції 'f(x)' використовується як вхідні дані для другої функції 'g(x)'.
const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
moveCoordinatesRight2Px,
doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]
Реалізуйте функцію memoizeTransform. Вона приймає функцію, яку потрібно запамʼятати, а потім повертає нову функцію, що запамʼятовує вхідні дані переданої функції, тож останнє повернене значення можна «запамʼятати» й обчислити лише раз, якщо функцію викличуть знову з тими самими аргументами.
Мемоізацію іноді використовують у динамічному програмуванні. Вона дозволяє виконувати дорогі операції лише один раз, адже їхні результати запамʼятовуються. Зауважте, що в цій вправі запамʼятовується лише останній результат, на відміну від деяких розвʼязків у динамічному програмуванні, які запамʼятовують усі результати.
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
Зареєструйтеся на Exercism, щоб вивчати й опановувати JavaScript, а також 37 концепцій159 вправ та справжнє наставництво від людей, і все це безкоштовно.