JavaScript | 第5章「関数」

javascrpit JavaScript
スポンサーリンク

関数スコープ・クロージャの動作を図解で解説(アニメーション付き)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>関数スコープとクロージャの図解</title>
  <script src="https://unpkg.com/framer-motion@10.16.4/dist/framer-motion.umd.js"></script>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-6">
  <h1 class="text-2xl font-bold mb-4">🧩 関数スコープとクロージャの動作図解</h1>

  <div id="animation" class="relative w-[600px] h-[400px] bg-white rounded-2xl shadow-lg overflow-hidden">
    <div id="outerScope" class="absolute top-4 left-4 bg-blue-100 border border-blue-400 rounded-xl p-4 w-[260px]">
      <p class="font-bold text-blue-700">外側スコープ (Global)</p>
      <p>let count = 0;</p>
    </div>

    <div id="functionBox" class="absolute top-[160px] left-[40px] bg-green-100 border border-green-400 rounded-xl p-4 w-[250px]">
      <p class="font-bold text-green-700">makeCounter()</p>
      <p>return function() { count++; }</p>
    </div>

    <div id="innerScope" class="absolute top-[120px] right-[40px] bg-yellow-100 border border-yellow-400 rounded-xl p-4 w-[250px] opacity-0">
      <p class="font-bold text-yellow-700">クロージャ(記憶された環境)</p>
      <p>count が保存される</p>
    </div>
  </div>

  <button id="play" class="mt-6 bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">▶ 再生</button>

  <script>
    const outer = document.getElementById('outerScope');
    const func = document.getElementById('functionBox');
    const inner = document.getElementById('innerScope');
    const play = document.getElementById('play');

    let step = 0;

    function animateStep() {
      step++;
      if (step === 1) {
        func.style.transition = 'all 1s';
        func.style.transform = 'translateX(250px)';
      } else if (step === 2) {
        inner.style.transition = 'opacity 1s';
        inner.style.opacity = '1';
      } else if (step === 3) {
        outer.style.backgroundColor = '#bfdbfe';
        inner.style.backgroundColor = '#fde68a';
        func.style.backgroundColor = '#bbf7d0';
      } else {
        step = 0;
        func.style.transform = 'translateX(0)';
        inner.style.opacity = '0';
        outer.style.backgroundColor = '#dbeafe';
        inner.style.backgroundColor = '#fef9c3';
        func.style.backgroundColor = '#dcfce7';
      }
    }

    play.addEventListener('click', animateStep);
  </script>

  <div class="mt-8 text-left max-w-lg bg-white p-4 rounded-xl shadow">
    <h2 class="text-lg font-bold mb-2">💡 解説</h2>
    <p class="mb-2">関数 <code>makeCounter</code> は、内部で <code>count</code> という変数を定義します。この変数は <strong>外部から直接は見えません</strong>。</p>
    <p class="mb-2">しかし <code>return function()</code> の部分で、<strong>そのスコープ内の変数を覚えたまま</strong> 新しい関数が作られます。これが「クロージャ」です。</p>
    <p>関数が呼び出されるたびに <code>count</code> の値が更新され、外部でもその状態を保ち続けます。</p>
  </div>
</body>
</html>
HTML

こちらが、関数スコープとクロージャの動作を図解アニメーション付きで理解できるHTMLデモです。
「▶ 再生」ボタンを押すと、makeCounter() 関数 → クロージャが生成 → スコープが保持される様子が順番に視覚化されます。

カウントアップ機能を追加

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>関数スコープとクロージャの図解+カウンター実演</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-6">
  <h1 class="text-2xl font-bold mb-4">🧩 関数スコープとクロージャの動作図解+カウンター実演</h1>

  <div id="animation" class="relative w-[600px] h-[400px] bg-white rounded-2xl shadow-lg overflow-hidden">
    <div id="outerScope" class="absolute top-4 left-4 bg-blue-100 border border-blue-400 rounded-xl p-4 w-[260px]">
      <p class="font-bold text-blue-700">外側スコープ (Global)</p>
      <p>let count = 0;</p>
    </div>

    <div id="functionBox" class="absolute top-[160px] left-[40px] bg-green-100 border border-green-400 rounded-xl p-4 w-[250px]">
      <p class="font-bold text-green-700">makeCounter()</p>
      <p>return function() { count++; }</p>
    </div>

    <div id="innerScope" class="absolute top-[120px] right-[40px] bg-yellow-100 border border-yellow-400 rounded-xl p-4 w-[250px] opacity-0">
      <p class="font-bold text-yellow-700">クロージャ(記憶された環境)</p>
      <p>count が保存される</p>
    </div>
  </div>

  <button id="play" class="mt-6 bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">▶ 再生</button>

  <script>
    const outer = document.getElementById('outerScope');
    const func = document.getElementById('functionBox');
    const inner = document.getElementById('innerScope');
    const play = document.getElementById('play');

    let step = 0;

    function animateStep() {
      step++;
      if (step === 1) {
        func.style.transition = 'all 1s';
        func.style.transform = 'translateX(250px)';
      } else if (step === 2) {
        inner.style.transition = 'opacity 1s';
        inner.style.opacity = '1';
      } else if (step === 3) {
        outer.style.backgroundColor = '#bfdbfe';
        inner.style.backgroundColor = '#fde68a';
        func.style.backgroundColor = '#bbf7d0';
      } else {
        step = 0;
        func.style.transform = 'translateX(0)';
        inner.style.opacity = '0';
        outer.style.backgroundColor = '#dbeafe';
        inner.style.backgroundColor = '#fef9c3';
        func.style.backgroundColor = '#dcfce7';
      }
    }

    play.addEventListener('click', animateStep);
  </script>

  <div class="mt-10 text-left max-w-lg bg-white p-4 rounded-xl shadow">
    <h2 class="text-lg font-bold mb-2">💡 クロージャの実際の動作を体験</h2>
    <p class="mb-3">下のボタンをクリックして、<code>makeCounter()</code> が内部の変数を保持している様子を体験してみましょう。</p>

    <div class="flex items-center gap-4 mt-3">
      <button id="countBtn" class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600">+1 カウント</button>
      <span id="countValue" class="text-xl font-bold text-green-700">0</span>
    </div>
  </div>

  <script>
    function makeCounter() {
      let count = 0; // 外部からは見えない変数
      return function() {
        count++;
        return count;
      };
    }

    const counter = makeCounter();
    const countBtn = document.getElementById('countBtn');
    const countValue = document.getElementById('countValue');

    countBtn.addEventListener('click', () => {
      const newCount = counter();
      countValue.textContent = newCount;
    });
  </script>

  <div class="mt-8 text-left max-w-lg bg-white p-4 rounded-xl shadow">
    <h2 class="text-lg font-bold mb-2">🧠 解説</h2>
    <p class="mb-2"><code>makeCounter</code> は内部に <code>count</code> 変数を持ち、それを更新する関数を返します。</p>
    <p class="mb-2">この関数が外で使われても、<code>count</code> の値は保持され続けます(これがクロージャ)。</p>
    <p>クリックしても変数がリセットされないのは、「関数の中の変数が記憶されている」ためです。</p>
  </div>
</body>
</html>
HTML

ボタンをクリックすると、クロージャによって count が保持され、実際に数字が1ずつ増えていく動作が確認できます。

タイトルとURLをコピーしました