0Pricing
JavaScript Academy · レッスン

クロージャが状態を取り込む仕組み

クロージャのレキシカル環境を理解します。

「クロージャが状態を取り込む仕組み」はCoddyKit上の無料JavaScript Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJavaScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 JavaScript Academyコースには全4レッスンが含まれています。

クロージャとは

クロージャとは、定義されたスコープにある変数への参照と関数をひとまとめにしたものです。内側の関数は、外側の関数が戻った後も、それらの変数を「覚えています」。

function outer() {
  const message = "hello";
  function inner() {
    console.log(message); // sees outer variable
  }
  inner();
}
outer(); // "hello"

内側の関数を返す

内側の関数を返すと、クロージャの仕組みが現れます。outerの実行がすでに終了していても、外側の変数へのアクセスを維持します。

function outer() {
  const message = "remembered";
  return function () {
    console.log(message);
  };
}
const fn = outer();
fn(); // "remembered"

レキシカルスコープ

クロージャが機能するのは、レキシカルスコープ(静的スコープ)の仕組みによるものです。関数は、呼び出された場所ではなく、ソースコード上で物理的に囲んでいるスコープに定義された変数へアクセスできます。

const x = 10;
function show() {
  console.log(x); // resolves where defined
}
function run() {
  const x = 99;
  show(); // still logs 10, not 99
}
run();

参照によるキャプチャ

クロージャがキャプチャするのは値のスナップショットではなく、変数そのものです。そのため、後から変数が変更されると、クロージャからは新しい値が見えます。

function make() {
  let count = 0;
  const get = () => count;
  count = 5; // changed after get defined
  return get;
}
console.log(make()()); // 5

呼び出しごとに新しいスコープが作られる

外側の関数を呼び出すたびに、新しい変数の集合が作成されます。異なる呼び出しから作られたクロージャ同士が状態を共有することはありません。

function counter() {
  let n = 0;
  return () => ++n;
}
const a = counter();
const b = counter();
console.log(a(), a()); // 1 2
console.log(b());      // 1 (independent)

パラメーターを取り込むクロージャ

関数のパラメーターも取り込まれます。これにより、汎用的な関数から特化した関数を作成できます。

function adder(x) {
  return (y) => x + y;
}
const add5 = adder(5);
console.log(add5(3));  // 8
console.log(add5(10)); // 15

古典的なループの罠

有名なバグがあります。ループ内でvarを使うと、すべてのクロージャで1つの変数が共有されます。クロージャが実行される頃にはループが終わっているため、すべてが最後の値を参照します。

const fns = [];
for (var i = 0; i < 3; i++) {
  fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 3 3 3

let による解決

letは反復ごとに新しいバインディングを作成するため、各クロージャが独自のiを取り込みます。これが現在の一般的な解決方法です。

const fns = [];
for (let i = 0; i < 3; i++) {
  fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 1 2

メモリを保持するクロージャ

クロージャが存在する限り、クロージャが取り込んだ変数はガベージコレクションの対象になりません。通常は問題ありませんが、必要以上に長くクロージャを保持するとメモリリークの原因になることがあります。

function make() {
  const big = new Array(3).fill("data");
  return () => big.length;
}
const f = make();
console.log(f()); // 3 - big stays in memory via f

クロージャはあらゆる場所で使われる

コールバック、イベントハンドラー、setTimeout、配列メソッド、Promiseはすべて、コンテキストを記憶するためにクロージャを利用しています。多くの場合、意識することなく常に使っています。

function greet(name) {
  return () => console.log("Hi " + name);
}
[greet("Ada"), greet("Bob")].forEach((fn) => fn());
// Hi Ada
// Hi Bob

取り込まれた状態を調べる

同じ呼び出しから作られた複数のクロージャは、同じ取り込まれた変数を共有します。そのため、一方が書き込んだ値をもう一方が読み取れます。

function pair() {
  let value = 0;
  return {
    set: (v) => { value = v; },
    get: () => value
  };
}
const p = pair();
p.set(42);
console.log(p.get()); // 42

クイックチェック

クロージャが状態を取り込む仕組み

まとめ

クロージャとは、関数と、その関数を定義したスコープへの参照を組み合わせたものです。レキシカルスコープにより、外側の関数が戻った後も外側の変数を記憶し、それらを値ではなく参照によって取り込みます。外側の関数を呼び出すたびに独立したスコープが作られます。ループの罠を避けるにはletを使います。クロージャは取り込んだメモリを保持し、あらゆる場所でコールバックを支えています。

よくある質問

「クロージャが状態を取り込む仕組み」レッスンは無料ですか?

はい。「クロージャが状態を取り込む仕組み」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、JavaScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 JavaScript Academyコースには全4レッスンが含まれています。

「クロージャが状態を取り込む仕組み」で何を学びますか?

クロージャのレキシカル環境を理解します。 ブラウザで直接実行するハンズオンコードでJavaScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

JavaScript Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJavaScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「クロージャが状態を取り込む仕組み」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJavaScript Academyレッスンでコードを書いて実行できますか?

はい。すべてのJavaScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. クロージャが状態を取り込む仕組み
  2. クロージャによるプライベート変数
  3. モジュールパターン(IIFE)
  4. ファクトリ関数
← JavaScript Academyに戻る