From a66f356b9b4ea6612ad4625d8354ae6d2730361c Mon Sep 17 00:00:00 2001 From: Masahiro FUJIMOTO Date: Tue, 1 Oct 2024 00:49:12 +0900 Subject: [PATCH] =?UTF-8?q?2024/08/20=20=E6=99=82=E7=82=B9=E3=81=AE?= =?UTF-8?q?=E8=8B=B1=E8=AA=9E=E7=89=88=E3=81=AB=E5=9F=BA=E3=81=A5=E3=81=8D?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/ja/web/javascript/closures/index.md | 40 ++++++++++------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/files/ja/web/javascript/closures/index.md b/files/ja/web/javascript/closures/index.md index a2738f11179e33..a45ff79777219a 100644 --- a/files/ja/web/javascript/closures/index.md +++ b/files/ja/web/javascript/closures/index.md @@ -2,12 +2,12 @@ title: クロージャ slug: Web/JavaScript/Closures l10n: - sourceCommit: 9c4fb236cd9ced12b1eb8e7696d8e6fcb8d8bad3 + sourceCommit: 2463abc1ca0fb6588d182651f8f659ae0d618915 --- {{jsSidebar("Intermediate")}} -**クロージャ**は、組み合わされた(囲まれた)関数と、その周囲の状態(**レキシカル環境**)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。 +**クロージャ**は、組み合わされた(囲まれた)関数と、その周囲の状態(**レキシカル環境**)への参照の組み合わせです。言い換えれば、クロージャは関数にその外側のスコープにアクセスする機能を提供します。JavaScript では、クロージャは関数が作成されるたびに、関数作成時点で作成されます。 ## レキシカルスコープ @@ -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) { @@ -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` 関数です。 @@ -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)としても知られるクロージャを使って、プライベートな関数と変数にアクセスできるパブリック関数を定義するにはこのようにします。 @@ -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` にアクセスできます。 @@ -249,13 +247,7 @@ console.log(counter2.value()); // 0. ## クロージャのスコープチェーン -すべてのクロージャには 3 つのスコープがあります。 - -- ローカルスコープ(独自のスコープ) -- 外側の関数スコープ -- グローバルスコープ - -よくある間違いは、外側の関数がネストされた関数である場合、外側の関数スコープへのアクセスにはさらに外側の関数スコープが含まれており。実質的にスコープチェーンが生じていることに気づかないことです。次のコードの例を考えてみましょう。 +入れ子になった関数が外側の関数のスコープにアクセスする際には、外側の関数の包含するスコープも含まれます。効果的に関数のスコープの連鎖を作成します。例えば、次の例のコードを考えてみましょう。 ```js // グローバルスコープ @@ -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(); ``` モジュール上のクロージャは、もっと興味深いものになります。 @@ -336,7 +330,7 @@ setX(6); console.log(getX()); // 6 ``` -クロージャはインポートされた値も隠蔽することができ、元の値が変わるとインポートされた値もそれに応じて変わるため、_ライブバインド_ と見なされます。 +クロージャはインポートされた値も隠蔽することができ、元の値が変わるとインポートされた値もそれに応じて変わるため、_ライブ{{Glossary("binding", "バインド")}}_ と見なされます。 ```js // myModule.js @@ -520,7 +514,7 @@ setupHelp(); 前回述べたように、それぞれの関数インスタンスは、独自のスコープとクロージャを管理します。したがって、クロージャが具体的なタスクに必要でない場合に、不必要に他の関数の中に関数を作成することは、処理速度とメモリー消費の両面でスクリプトのパフォーマンスに悪影響を与えるので、賢明ではありません。 -例えば、新しくオブジェクト/クラスを作成する時、一般的にメソッドはオブジェクトのコンストラクターの中で定義するのではなく、オブジェクトのプロトタイプに結びつけるべきです。コンストラクタの中で定義してしまうと、コンストラクターが呼び出されるたびに(つまりオブジェクトが作成されるたびに)メソッドが再代入されてしまうことになるからです。 +例えば、新しくオブジェクト/クラスを作成する時、一般的にメソッドはオブジェクトのコンストラクターの中で定義するのではなく、オブジェクトのプロトタイプに結びつけるべきです。コンストラクターの中で定義してしまうと、コンストラクターが呼び出されるたびに(つまりオブジェクトが作成されるたびに)メソッドが再代入されてしまうことになるからです。 以下のケースを考えてみてください。