Skip to content

Commit

Permalink
2024/08/20 時点の英語版に基づき更新
Browse files Browse the repository at this point in the history
  • Loading branch information
mfuji09 committed Oct 5, 2024
1 parent 4a65312 commit a66f356
Showing 1 changed file with 17 additions and 23 deletions.
40 changes: 17 additions & 23 deletions files/ja/web/javascript/closures/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
title: クロージャ
slug: Web/JavaScript/Closures
l10n:
sourceCommit: 9c4fb236cd9ced12b1eb8e7696d8e6fcb8d8bad3
sourceCommit: 2463abc1ca0fb6588d182651f8f659ae0d618915
---

{{jsSidebar("Intermediate")}}

**クロージャ**は、組み合わされた(囲まれた)関数と、その周囲の状態(**レキシカル環境**)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます
**クロージャ**は、組み合わされた(囲まれた)関数と、その周囲の状態(**レキシカル環境**)への参照の組み合わせです。言い換えれば、クロージャは関数にその外側のスコープにアクセスする機能を提供します。JavaScript では、クロージャは関数が作成されるたびに、関数作成時点で作成されます

## レキシカルスコープ

Expand All @@ -25,15 +25,13 @@ function init() {
init();
```

`init()` 関数はローカル変数 `name` を作成し、それから関数 `displayName()` を定義しています。`displayName()``init()` の中で定義されている内部関数で、その関数本体の内部でしか利用できません。`displayName()` 自体はローカル変数を持っていませんが、外側の関数で宣言された変数にアクセスできるので`displayName()` では親関数 `init()` で宣言された変数 `name` を使用できます。しかし、 `displayName()` に同じローカル変数があればそれが使われます。
`init()` 関数はローカル変数 `name` を作成し、それから関数 `displayName()` を定義しています。`displayName()``init()` の中で定義されている内部関数で、その関数本体の内部でしか利用できません。`displayName()` 自体はローカル変数を持っていませんが、外側のスコープで宣言された変数にアクセスできるので`displayName()` では親関数 `init()` で宣言された変数 `name` を使用できます。しかし、 `displayName()` に同じローカル変数があればそれが使われます。

[この JSFiddle リンク](https://jsfiddle.net/3dxck52m/)を使用してコードを実行すると、`displayName()` 関数内の `console.log()` 文が、その親関数で宣言されている `name` 変数の値を正常に表示していることに注意してください。これはレキシカルスコープの例で、関数が入れ子になっているときにパーサーがどのように変数名を解決するかを記述したものです。レキシカルという言葉は、レキシカルスコープがソースコード内で変数が宣言された場所を使用して、その変数が利用できる場所を決定するという事実を示しています。入れ子の関数は、その外側のスコープで宣言された変数にアクセスすることができます。

この具体例では、変数が宣言された関数本体の中でしかアクセスできないため、スコープは関数スコープと呼ばれます。

### let と const のスコープ

従来(ES6 以前)、JavaScript には関数スコープとグローバルスコープの 2 種類のスコープしかありませんでした。`var` で宣言された変数は、関数内で宣言されたか、関数外で宣言されたかによって、関数スコープかグローバルスコープかのどちらかになります。中括弧で囲まれたブロックはスコープを作成しないので、これはやっかいなことです。
従来(ES6 以前)、JavaScript の変数には関数スコープとグローバルスコープの 2 種類のスコープしかありませんでした。`var` で宣言された変数は、関数内で宣言されたか、関数外で宣言されたかによって、関数スコープかグローバルスコープかのどちらかになります。中括弧で囲まれたブロックはスコープを作成しないので、これはやっかいなことです。

```js
if (Math.random() > 0.5) {
Expand Down Expand Up @@ -80,7 +78,7 @@ myFunc();

このコードが動作するということは直感的に理解できないかもしれません。いくつかのプログラミング言語では、関数内部のローカル変数はその関数が実行されている間だけ存在します。一旦 `makeFunc()` の実行が完了したら、name 変数はもう必要とされなくなると考えた方が筋は通っています。ただこのコードが期待したとおりに動くという事は、これは明らかに JavaScript にはあてはまりません。

この理由は、JavaScript の関数はクロージャとなるためです。クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。この場合、`myFunc``makeFunc` が実行された時に作られた `displayName` 関数のインスタンスへの参照です。`displayName` のインスタンスはレキシカル環境への参照を保持し、そこに `name` 変数が存在します。このため、`makeFunc` が実行された時に、`name` 変数が残っていて "Mozilla" が `console.log` に渡されます。
この理由は、JavaScript の関数はクロージャとなるためです。クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆるローカル変数によって構成されています。この場合、`myFunc``makeFunc` が実行された時に作られた `displayName` 関数のインスタンスへの参照です。`displayName` のインスタンスはレキシカル環境への参照を保持し、そこに `name` 変数が存在します。このため、`makeFunc` が実行された時に、`name` 変数が残っていて "Mozilla" が `console.log` に渡されます。

ここにもう少し面白い例があります。`makeAdder` 関数です。

Expand Down Expand Up @@ -165,7 +163,7 @@ document.getElementById("size-16").onclick = size16;

Java などの言語ではプライベートなメソッドを宣言することが出来ます。これは同じクラス内にあるほかのメソッドからのみ呼び出せるメソッドのことです。

JavaScript には、[クラス](/ja/docs/Web/JavaScript/Reference/Classes)が登場するまで、[プライベートメソッド](/ja/docs/Web/JavaScript/Reference/Classes/Private_class_fields#プライベートメソッド)を宣言するネイティブの方法はありませんでしたが、クロージャを使うとプライベートメソッドを模倣することができます。プライベートメソッドはコードへのアクセスを制限するのに役立つだけではなく、コードのパブリックインターフェイスが不要なメソッドでいっぱいになるのを防ぐため、グローバル名前空間を管理するのに非常に有効です。
JavaScript には、[クラス](/ja/docs/Web/JavaScript/Reference/Classes)が登場するまで、[プライベートメソッド](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties#プライベートメソッド)を宣言するネイティブの方法はありませんでしたが、クロージャを使うとプライベートメソッドを模倣することができます。プライベートメソッドはコードへのアクセスを制限するのに役立つだけではなく、コードのパブリックインターフェイスが不要なメソッドでいっぱいになるのを防ぐため、グローバル名前空間を管理するのに非常に有効です。

[モジュールパターン](https://www.google.com/search?q=javascript+module+pattern)としても知られるクロージャを使って、プライベートな関数と変数にアクセスできるパブリック関数を定義するにはこのようにします。

Expand Down Expand Up @@ -203,7 +201,7 @@ console.log(counter.value()); // 1.

ここでは色々なことが行われています。前の例ではクロージャがそれぞれ独自の環境を持っていましたが、この例では環境が 1 つだけ作成され、その環境は `counter.increment`, `counter.decrement`, `counter.value` という 3 つの関数によって共有されています。

この共有レキシカル環境は、_定義されるとすぐに実行される_[IIFE](/ja/docs/Glossary/IIFE) とも呼ばれます)無名関数の本文で作成されています。この環境は変数 `privateCounter` と関数 `changeBy` という 2 つのプライベートアイテムを含んでいます。これらはどちらも無名関数の外側からは直接アクセス出来ません。その代わり、この無名ラッパー関数から返される 3 つのパブリック関数からはアクセスできます
この共有レキシカル環境は、_定義されるとすぐに実行される_[IIFE](/ja/docs/Glossary/IIFE) とも呼ばれます)無名関数の本文で作成されています。この環境は変数 `privateCounter` と関数 `changeBy` という 2 つのプライベートアイテムを含んでいます。これらはどちらも無名関数の外側からは直接アクセス出来ません。その代わり、この無名ラッパー関数から返される 3 つのパブリック関数からは間接的にアクセスします

これら 3 つのパブリック関数は同じ環境を共有するクロージャです。JavaScript のレキシカルスコープにより、これらの関数はそれぞれが変数 `privateCounter` と関数 `changeBy` にアクセスできます。

Expand Down Expand Up @@ -249,13 +247,7 @@ console.log(counter2.value()); // 0.
## クロージャのスコープチェーン

すべてのクロージャには 3 つのスコープがあります。

- ローカルスコープ(独自のスコープ)
- 外側の関数スコープ
- グローバルスコープ

よくある間違いは、外側の関数がネストされた関数である場合、外側の関数スコープへのアクセスにはさらに外側の関数スコープが含まれており。実質的にスコープチェーンが生じていることに気づかないことです。次のコードの例を考えてみましょう。
入れ子になった関数が外側の関数のスコープにアクセスする際には、外側の関数の包含するスコープも含まれます。効果的に関数のスコープの連鎖を作成します。例えば、次の例のコードを考えてみましょう。

```js
// グローバルスコープ
Expand Down Expand Up @@ -299,20 +291,22 @@ const result = sum4(4);
console.log(result); // 20
```

上記の例では、一連のネストされた関数があり、それらはすべて外側の関数スコープにアクセスできます。この文脈では、クロージャは*すべて*の外側の関数スコープにアクセスできると言えます
上記の例では、一連のネストされた関数があり、それらはすべて外側の関数スコープにアクセスできます。この文脈では、クロージャは*すべて*の外側のスコープにアクセスできると言えます

クロージャはブロックスコープやモジュールスコープの変数も取り込むことができます。例えば、以下はブロックスコープにある変数 `y` に対してクロージャを作成します。

```js
function outer() {
const x = 5;
if (Math.random() > 0.5) {
let getY;
{
const y = 6;
return () => console.log(x, y);
getY = () => y;
}
console.log(typeof y); // undefined
console.log(getY()); // 6
}

outer()(); // Logs 5 6
outer();
```

モジュール上のクロージャは、もっと興味深いものになります。
Expand All @@ -336,7 +330,7 @@ setX(6);
console.log(getX()); // 6
```

クロージャはインポートされた値も隠蔽することができ、元の値が変わるとインポートされた値もそれに応じて変わるため、_ライブバインド_ と見なされます。
クロージャはインポートされた値も隠蔽することができ、元の値が変わるとインポートされた値もそれに応じて変わるため、_ライブ{{Glossary("binding", "バインド")}}_ と見なされます。

```js
// myModule.js
Expand Down Expand Up @@ -520,7 +514,7 @@ setupHelp();

前回述べたように、それぞれの関数インスタンスは、独自のスコープとクロージャを管理します。したがって、クロージャが具体的なタスクに必要でない場合に、不必要に他の関数の中に関数を作成することは、処理速度とメモリー消費の両面でスクリプトのパフォーマンスに悪影響を与えるので、賢明ではありません。

例えば、新しくオブジェクト/クラスを作成する時、一般的にメソッドはオブジェクトのコンストラクターの中で定義するのではなく、オブジェクトのプロトタイプに結びつけるべきです。コンストラクタの中で定義してしまうと、コンストラクターが呼び出されるたびに(つまりオブジェクトが作成されるたびに)メソッドが再代入されてしまうことになるからです。
例えば、新しくオブジェクト/クラスを作成する時、一般的にメソッドはオブジェクトのコンストラクターの中で定義するのではなく、オブジェクトのプロトタイプに結びつけるべきです。コンストラクターの中で定義してしまうと、コンストラクターが呼び出されるたびに(つまりオブジェクトが作成されるたびに)メソッドが再代入されてしまうことになるからです。

以下のケースを考えてみてください。

Expand Down

0 comments on commit a66f356

Please sign in to comment.