クロージャは、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変換を使ってWebページを作ってきました。話し合いの結果、一部の計算をJavaScriptで動的に行うことにしました。チームメイトの中にはJavaScriptの経験が浅い人もいるため、関数クロージャを使い、{x, y}の座標ペアを繰り返し変換できるようにすることにしました。
translate2d関数を実装しましょう。この関数はクロージャを利用して、座標ペアの2D平行移動を繰り返し実行する関数を返します。
幾何学において、平行移動とは、点やベクトル、図形を同じ方向に同じ距離だけ動かすことを指します。 これは、すべての点に定数を足すことだと解釈できます。
const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]
scale2d関数を実装しましょう。この関数はクロージャを利用して、座標ペアの2D拡大縮小を繰り返し実行する関数を返します。
幾何学において、一様な拡大縮小とは、ベクトルや図形を同じ方向に拡大または縮小することを指します。 これは、すべての点に定数(拡大率)を掛けることだと解釈できます。
この演習では、正の拡大率のみを扱うものとします。
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
2つの変換関数を組み合わせて、繰り返し実行できる変換を作ります。これは一般に_関数合成_と呼ばれ、1つ目の関数_'f(x)'の結果を2つ目の関数'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関数を実装しましょう。この関数は_メモ化_する関数を受け取り、渡された関数への入力を覚えておく新しい関数を返します。これにより、最後の戻り値を「記憶」しておき、同じ引数で再び呼び出されたときは1回だけ計算すればよくなります。
メモ化は、_動的計画法_で使われることがあります。 結果が記憶されるため、処理の重い操作を1回だけ実行すればよくなります。 注意: この演習では、_すべて_の結果をメモ化する動的計画法の一部の解法とは異なり、最後の結果だけを記憶します。
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に登録すれば、37個のコンセプト159個の演習、そして本物の人間によるメンタリングとともに、JavaScriptを学んでマスターできます。すべて無料です。