---
type: learn
language: javascript
slug: function/scope
title: "スコープと閉包 — 変数はどこから見えるか"
title_tag: "JavaScript のスコープと閉包 — var と let の違い【第2回】"
summary: >
  変数が見える範囲と、関数が「作られた場所の変数を覚えている」という性質を扱います。
  ループの中で作った関数が全部同じ値になる有名な罠と、その原因までを実行して確かめます。
description: >
  ループの中で作った関数が全部 3 になる理由を、var と let を並べて実行して示します。
  閉包で設定や状態を閉じ込める書き方、見えなくなる変数の追い方まで確かめられます。
status: published
difficulty: 2
minutes: 9

versions:
  verified: "Node 22.22.3"
  since: "ES2015"
  deprecated: null
  removed: null

sources:
  - title: "Declarative Environment Records — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-declarative-environment-records"
  - title: "CreatePerIterationEnvironment — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-createperiterationenvironment"
  - title: "クロージャ — MDN"
    url: "https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Closures"

terms: [スコープ, 閉包, ブロックスコープ, 巻き上げ]

links:
  prev: javascript/learn/function/basics
  next: javascript/learn/function/higher-order
  related:
    - javascript/errors/cannot-access-before-initialization
    - javascript/errors/is-not-defined

content_updated_at: 2026-09-08
published_at: 2026-09-08
---

**変数は、書かれた場所から外へは見えません。**

```js run
const outer = '外';

function show() {
  const inner = '内';

  return outer + inner;
}

console.log(show());
```
```output
外内
```

中からは外が見えます。**逆は見えません。**

```js bad
function show() {
  const inner = '内';
}

show();

console.log(inner);
```
```output
ReferenceError: inner is not defined
```

→ [ReferenceError: x is not defined](/ja/javascript/errors/is-not-defined/)

## `{}` でも区切られる

`let` と `const` は、**関数だけでなくブロックでも区切られます。**

```js run
let n = 1;

{
  let n = 2;
  console.log('中', n);
}

console.log('外', n);
```
```output
中 2
外 1
```

同じ名前ですが、**別の変数**です。中の `n` は外を隠しているだけで、書き換えてはいません。

[bad:外を使いたいのに隠していると、見つけにくい間違いになります。]
→ [ReferenceError: Cannot access 'x' before initialization](/ja/javascript/errors/cannot-access-before-initialization/)

## 関数は、作られた場所を覚えている

**ここが要点です。**

```js run
function makeCounter() {
  let count = 0;

  return () => {
    count++;
    return count;
  };
}

const counter = makeCounter();

console.log(counter(), counter(), counter());
```
```output
1 2 3
```

`makeCounter()` は**もう終わっている**のに、`count` は残っています。

返した関数が `count` を[key:使っている]ので、消えずに保たれます。
これを[type:閉包（クロージャ）]と呼びます。

### 呼ぶたびに別の入れ物ができる

```js run
function makeCounter() {
  let count = 0;

  return () => {
    count++;
    return count;
  };
}

const a = makeCounter();
const b = makeCounter();

console.log(a(), a(), a());
console.log(b());
```
```output
1 2 3
1
```

`b` は `1` から始まります。**`a` と `b` は別の `count` を持っています。**

`makeCounter()` を呼ぶたびに新しい `count` が作られ、
返した関数がそれぞれ自分のぶんを覚えています。

## 何に使うか

### 設定を閉じ込める

```js run
function makeTag(prefix) {
  return (text) => `${prefix}: ${text}`;
}

const warn = makeTag('警告');
const info = makeTag('情報');

console.log(warn('壊れた'));
console.log(info('動いた'));
```
```output
警告: 壊れた
情報: 動いた
```

`prefix` を毎回渡さなくて済みます。
**「先に決まるもの」と「あとで来るもの」を分けられます。**

### 外から触れない状態を持つ

`count` は外から直接書き換えられません。
**関数を通してしか変えられない**ので、想定外の値になりません。

## いちばん有名な罠

```js bad
const fns = [];

for (var i = 0; i < 3; i++) {
  fns.push(() => i);
}

console.log(fns.map((f) => f()));
```
```output
[ 3, 3, 3 ]
```

**3つとも `3` です。** `0, 1, 2` にはなりません。

`var` で作った `i` は[key:ループ全体で1つ]しかありません。
3つの関数は同じ `i` を覚えていて、ループが終わったときの値は `3` です。

`let` にすると直ります。

```js run
const fns = [];

for (let i = 0; i < 3; i++) {
  fns.push(() => i);
}

console.log(fns.map((f) => f()));
```
```output
[ 0, 1, 2 ]
```

`let` は**繰り返しごとに新しい `i` を作ります。**
だから、それぞれの関数が別の値を覚えます。

[em:`var` を使わない理由の中で、これがいちばん実害の大きいものです。]

| | 作られる数 | 見える範囲 |
|---|---|---|
| `var` | ループ全体で1つ | 関数全体 |
| `let` / `const` | **繰り返しごと** | ブロックの中 |

## 覚えているのは「値」ではなく「変数」

```js run
let message = '最初';

const show = () => message;

console.log(show());

message = 'あとで変えた';

console.log(show());
```
```output
最初
あとで変えた
```

関数が覚えているのは[key:変数そのもの]です。作られた時点の値ではありません。

**だから、あとで変えた値が見えます。**
上の `var` の罠も、これと同じことが起きていました。

## よくある間違い

### 閉じ込めたつもりで共有している

```js bad
let count = 0;

function makeCounter() {
  return () => {
    count++;
    return count;
  };
}

const a = makeCounter();
const b = makeCounter();

console.log(a(), a());
console.log(b());
```
```output
1 2
3
```

`count` が関数の**外**にあるので、`a` と `b` が同じものを触っています。
`b` が `3` から始まりました。

**閉じ込めたい変数は、関数の中で宣言してください。**

## まとめ

- 変数は[key:書かれた場所]から外へは見えない。中からは外が見える
- `let` / `const` は `{}` でも区切られる
- 関数は**作られた場所の変数を覚えている**（[type:閉包]）
- 呼ぶたびに[em:新しい入れ物]ができる
- **`var` はループ全体で1つ。** 中で作った関数が全部同じ値になる
- 覚えているのは値ではなく[key:変数]。あとで変えた値が見える

## 次に学ぶ

関数を[key:値として渡す]書き方を学びます。
`map()` に関数を渡す形の正体、関数を返す関数、
そして「関数を受け取って新しい関数を作る」形までやります。

→ [第3回: 関数を値として扱う](/ja/javascript/learn/function/higher-order/)
