読み込み中...

JavaScriptのクロージャを徹底解説!安心の使い方10選+サンプルコード

JavaScriptクロージャのイメージ JS
この記事は約12分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptのクロージャは、初めて触れる方にとっては少し難しく感じるかもしれません。

しかし、この記事を読み進めることで、クロージャの概念を理解し、実際のコーディングで活用できるようになります。

クロージャは、JavaScriptの強力な機能の一つで、適切に使用することで、コードの可読性と保守性を向上させることができます。

●クロージャとは

クロージャは、JavaScriptプログラミングにおいて非常に重要な概念です。

初心者の方々にとっては少し抽象的に感じるかもしれませんが、理解することで、より効果的なコードを書くことができるようになります。

ここでは、クロージャの基本概念について詳しく説明します。

○クロージャの基本概念

クロージャという言葉を聞いて、何のことかピンとこない方も多いでしょう。

クロージャとは、関数と、その関数が参照できる外部の変数環境を一緒にまとめたものです。

言い換えれば、関数の中で定義された変数が、その関数の実行が終わった後でも生き続けるような仕組みです。

これにより、変数のスコープを制御し、データのプライバシーを保護することができます。

●クロージャの使い方

クロージャの概念を理解したら、次は実際の使い方を見ていきましょう。

クロージャは様々な場面で活用することができ、コードの柔軟性と再利用性を高めるのに役立ちます。

ここでは、クロージャの代表的な使用例をいくつか紹介します。

それぞれの例を通じて、クロージャがどのように機能し、どのような利点があるのかを学んでいきましょう。

○サンプルコード1:カウンター機能

クロージャの基本的な使い方を理解するために、まずは簡単なカウンター機能を実装してみましょう。

このサンプルコードでは、クロージャを使って、カウントの状態を保持しながら、呼び出すたびに値を増加させる関数を作成します。

function createCounter() {
  let count = 0;

  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();

counter(); // 1
counter(); // 2
counter(); // 3

このコードでは、createCounter関数内で定義されたcount変数が、返された関数によってクロージャとして保持されています。

そのため、counterを呼び出すたびに、countの値が増加し、その値が表示されます。

○サンプルコード2:プライベート変数の実現

クロージャを使用することで、オブジェクト指向プログラミングにおけるプライベート変数のような概念を実現することができます。

この方法は、データのカプセル化を行い、外部からの不正なアクセスを防ぐのに役立ちます。

次のサンプルコードでは、名前と年齢を持つ人物オブジェクトを作成し、それらの情報に直接アクセスできないようにします。

function createPerson(name, age) {
  let _name = name;
  let _age = age;

  return {
    getName: function () {
      return _name;
    },
    getAge: function () {
      return _age;
    },
  };
}

const person = createPerson("John Doe", 30);
console.log(person.getName()); // "John Doe"
console.log(person.getAge()); // 30

この例では、_name_ageがプライベート変数として機能し、直接アクセスすることはできません。

代わりに、getNamegetAgeメソッドを通じてのみ、これらの値を取得することができます。

○サンプルコード3:イベントリスナーのスコープ

ウェブ開発においては、イベントリスナーを使用する機会が多くあります。

クロージャを利用することで、イベントリスナー内で使用する変数のスコープを制限し、グローバルスコープの汚染を防ぐことができます。

これは特に、複数の要素に対して同様のイベントリスナーを設定する場合に有用です。

document.querySelector("#button").addEventListener("click", (function () {
  let count = 0;

  return function () {
    count++;
    console.log("Button clicked " + count + " times.");
  };
})());

このコードでは、即時実行関数式(IIFE)を使用してクロージャを作成しています。

count変数はクロージャ内に閉じ込められているため、ボタンがクリックされるたびに増加しますが、外部からアクセスすることはできません。

○サンプルコード4:タイマー機能

クロージャを使用して、経過時間を計測するタイマー機能を実装することができます。

この例では、タイマーの開始時間を保持し、呼び出されるたびに経過時間を計算して表示します。

これは、パフォーマンス測定やユーザーの操作時間の追跡などに利用できる便利な機能です。

function createTimer() {
  let startTime = Date.now();

  return function () {
    const elapsedTime = Date.now() - startTime;
    console.log("Elapsed time: " + elapsedTime + "ms");
  };
}

const timer = createTimer();
setTimeout(() => {
  timer(); // Elapsed time: 経過時間(ms)が表示される
}, 1000);

createTimer関数は、開始時間をstartTime変数に保存し、その変数にアクセスできる関数を返します。

この返された関数が呼び出されるたびに、現在の時間と開始時間の差分を計算して表示します。

○サンプルコード5:メモ化

計算コストの高い関数の結果をキャッシュする「メモ化」という技術があります。

クロージャを使用することで、この機能を簡単に実装することができます。

メモ化は、同じ入力に対して常に同じ出力を返す純粋関数に特に有効で、プログラムの実行速度を大幅に向上させることができます。

ここでは、階乗計算をメモ化する例を紹介します。

function memoize(fn) {
  const cache = {};

  return function (arg) {
    if (cache[arg]) {
      return cache[arg];
    }

    const result = fn(arg);
    cache[arg] = result;
    return result;
  };
}

function factorial(n) {
  if (n === 0) {
    return 1;
  }
  return n * factorial(n - 1);
}

const memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5)); // 120
console.log(memoizedFactorial(5)); // 120 (キャッシュから取得)

この例では、memoize関数がクロージャを利用して、計算結果をキャッシュします。

2回目以降の同じ引数での呼び出しでは、計算せずにキャッシュから結果を返すため、処理速度が向上します。

○サンプルコード6:モジュールパターン

JavaScriptでは、クロージャを利用してモジュールパターンを実現することができます。

これにより、変数やメソッドのカプセル化が可能になり、名前空間の汚染を防ぐことができます。

モジュールパターンは、大規模なアプリケーション開発において、コードの構造化と保守性の向上に役立ちます。

const myModule = (function () {
  let privateVar = "I am private.";

  function privateMethod() {
    console.log(privateVar);
  }

  return {
    publicMethod: function () {
      privateMethod();
    },
  };
})();

myModule.publicMethod(); // "I am private."

この例では、即時実行関数式を使用してモジュールを作成しています。

privateVarprivateMethodは外部からアクセスできませんが、publicMethodを通じて間接的に使用することができます。

○サンプルコード7:一度だけ実行する関数

クロージャを使用すると、特定の関数を一度だけ実行するような制御を簡単に実装することができます。

これは、初期化処理や、特定の条件下でのみ実行したい処理に有用です。また、重複実行を防ぎたい場合にも使えます。

function createOnce(fn) {
  let called = false;

  return function () {
    if (!called) {
      called = true;
      return fn.apply(this, arguments);
    }
  };
}

const logOnce = createOnce(console.log);
logOnce("Hello!"); // "Hello!"
logOnce("Hello!"); // 何も表示されない

この例では、createOnce関数が、渡された関数を最初の呼び出し時にのみ実行するようなラッパー関数を返します。

2回目以降の呼び出しでは何も実行されません。

●クロージャの注意点と対処法

クロージャは強力な機能ですが、使い方を誤ると予期せぬ問題を引き起こす可能性があります。

ここでは、クロージャを使用する際の主な注意点とその対処法について説明します。

○メモリリークの対策

クロージャを使用する際は、メモリリークに注意を払う必要があります。

クロージャは、その性質上、変数を長期間保持し続けるため、不適切な使用はメモリの無駄遣いにつながる可能性があります。

メモリリークを防ぐためには、クロージャが不要になった時点で、参照を解除することが重要です。

例えば、イベントリスナーを使用する場合、イベントリスナーを削除する際に、関連するクロージャも解放するようにしましょう。

また、大量のデータを扱うクロージャを作成する場合は、WeakMapやWeakSetなどの弱参照を利用することで、ガベージコレクションを助けることができます。

●クロージャの応用例とサンプルコード

ここまでクロージャの基本的な使い方を見てきましたが、実際の開発ではさらに高度な使い方も可能です。

ここでは、クロージャの応用例をいくつか紹介します。

この例を通じて、クロージャがいかに柔軟で強力なツールであるかを理解していただけるでしょう。

○サンプルコード8:デコレータパターン

クロージャを使用して、デコレータパターンを実装することができます。

デコレータパターンは、既存の関数に新しい機能を追加する際に便利です。

これにより、コードの再利用性を高め、機能の追加や変更を柔軟に行うことができます。

ここでは、ログ出力機能を追加するデコレータの例を紹介します。

function withLogging(fn) {
  return function () {
    console.log("Executing " + fn.name);
    return fn.apply(this, arguments);
  };
}

function add(a, b) {
  return a + b;
}

const loggedAdd = withLogging(add);
console.log(loggedAdd(1, 2)); // "Executing add" と表示され、結果は 3

この例では、withLogging関数が元の関数をラップし、実行前にログを出力する新しい関数を返しています。

これにより、元の関数の動作を変更することなく、ログ出力機能を追加することができます。

○サンプルコード9:イベントデリゲーション

クロージャを利用してイベントデリゲーションを実装することで、多数の子要素に対して効率的にイベントリスナーを設定することができます。

これは特に、動的に要素が追加されるような場合に有用です。

document.querySelector("#parent").addEventListener("click", function (event) {
  if (event.target.matches(".child")) {
    console.log("Child element clicked:", event.target);
  }
});

この例では、親要素にイベントリスナーを設定し、クリックされた要素が特定のセレクタに一致する場合にのみ処理を実行します。

これにより、多数の子要素に個別にイベントリスナーを設定する必要がなくなり、メモリ使用量とパフォーマンスが改善されます。

○サンプルコード10:スロットリング

クロージャを使用してスロットリング機能を実装することで、頻繁に発生するイベント(スクロールやリサイズなど)の処理頻度を制限し、パフォーマンスを向上させることができます。

これは特に、リアルタイムで更新が必要なUI要素がある場合に役立ちます。

function throttle(fn, delay) {
  let lastCall = 0;

  return function () {
    const now = Date.now();
    if (now - lastCall >= delay) {
      lastCall = now;
      return fn.apply(this, arguments);
    }
  };
}

function onScroll() {
  console.log("Scroll event detected.");
}

window.addEventListener("scroll", throttle(onScroll, 500));

この例では、throttle関数が、指定された遅延時間内に一度だけ関数を実行するようにします。

これにより、スクロールイベントのような頻繁に発生するイベントの処理回数を減らし、アプリケーションのパフォーマンスを向上させることができます。

まとめ

この記事では、JavaScriptにおけるクロージャについて、基本的な概念から実践的な使用例まで、幅広く解説しました。

初心者の方々にとっては、クロージャの概念を完全に理解し、自在に使いこなすまでには時間がかかるかもしれません。

しかし、この記事で紹介した10個のサンプルコードを一つずつ試し、理解を深めていくことで、クロージャの便利さと重要性を実感できるはずです。