diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/ja/web/javascript/closures/index.html | |
parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip |
initial commit
Diffstat (limited to 'files/ja/web/javascript/closures/index.html')
-rw-r--r-- | files/ja/web/javascript/closures/index.html | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/files/ja/web/javascript/closures/index.html b/files/ja/web/javascript/closures/index.html new file mode 100644 index 0000000000..e163ea07ac --- /dev/null +++ b/files/ja/web/javascript/closures/index.html @@ -0,0 +1,468 @@ +--- +title: クロージャ +slug: Web/JavaScript/Closures +tags: + - Closure + - ES5 + - Guide + - Intermediate + - JavaScript + - Reference +translation_of: Web/JavaScript/Closures +--- +<div>{{jsSidebar("Intermediate")}}</div> + +<p><strong>クロージャ</strong>は、組み合わされた(囲まれた)関数と、その周囲の状態(<strong>レキシカル環境</strong>)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。</p> + +<h2 id="Lexical_scoping" name="Lexical_scoping">レキシカルスコープ</h2> + +<p>次のような関数を考えます。</p> + +<div> +<pre class="brush:js notranslate">function init() { + var name = 'Mozilla'; // name は、init が作成するローカル変数 + + function displayName() { // displayName() は内部に閉じた関数 + alert(name); // 親関数で宣言された変数を使用 + } + displayName(); +} +init();</pre> +</div> + +<p><code>init()</code> 関数はローカル変数 <code>name</code> を作成し、それから関数 <code>displayName()</code> を定義しています。<code>displayName()</code> は <code>init()</code> の中で定義されている内部関数で、その関数本体の内部でしか利用できません。<code>displayName()</code> 自体はローカル変数を持っていませんが、外側の関数で宣言された変数にアクセスできるので、<code>displayName()</code> では親関数 <code>init()</code> で宣言された変数 <code>name</code> を使用できます。しかし、 <code>displayName()</code> に同じローカル変数があればそれが使われます。</p> + +<p>コードを <a href="http://jsfiddle.net/xAFs9/3/" title="http://jsfiddle.net/xAFs9/">JSFiddle で実行</a>して、<code>displayName()</code> 関数の <code>alert()</code> 文が、親関数で定義された <code>name</code> 変数の値を表示するのを確認してください。これは<em>レキシカルスコープ</em>、つまり関数がネストされている時にパーサーが変数名を解決する方法を示す例です。<em>レキシカル</em>という言葉の触れるところは、変数のスコープはソースコード内の位置によって決定され、ネストされた関数は外側のスコープで宣言された変数にアクセスすることができます。</p> + +<h2 id="Closure" name="Closure">クロージャ</h2> + +<p>今度は次のような例を考えます。</p> + +<pre class="brush: js notranslate">function makeFunc() { + var name = 'Mozilla'; + function displayName() { + alert(name); + } + return displayName; +} + +var myFunc = makeFunc(); +myFunc(); +</pre> + +<p>このコードを実行すると、前の <code>init()</code> の例と全く同じように文字列 "Mozilla" が JavaScript の警告ボックスに表示されます。前の例とは異なる、興味深い点は、内部関数 <code>displayName()</code> がそれが実行される前に外側の関数から返されているという事です。</p> + +<p>このコードが動作するということは直感的に理解できないかもしれません。いくつかのプログラミング言語では、関数内部のローカル変数はその関数が実行されている間だけ存在します。一旦 <code>makeFunc()</code> の実行が完了したら、name 変数はもう必要とされなくなると考えた方が筋は通っています。ただこのコードが期待したとおりに動くという事は、これは明らかに JavaScript にはあてはまりません。</p> + +<p>この理由は、JavaScript の関数は<em>クロージャとなるためで</em>す。クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。この場合、<code>myFunc</code> は <code>makeFunc</code> が実行された時に作られた <code>displayName</code> 関数のインスタンスへの参照です。<code>displayName</code> のインスタンスはレキシカル環境への参照を保持し、そこに <code>name</code> 変数が存在します。このため、<code>makeFunc</code> が実行された時に、<code>name</code> 変数が残っていて "Mozilla" が <code>alert</code> に渡されます。</p> + +<p>ここにもう少し面白い例があります。<code>makeAdder</code> 関数です。</p> + +<pre class="brush:js notranslate">function makeAdder(x) { + return function(y) { + return x + y; + }; +} + +var add5 = makeAdder(5); +var add10 = makeAdder(10); + +console.log(add5(2)); // 7 と表示される +console.log(add10(2)); // 12 と表示される +</pre> + +<p>この例では、1 つの引数 <code>x</code> を取り、新しい関数を返す <code>makeAdder(x)</code> 関数を定義しています。返される関数は 1 つの引数 <code>y</code> を取り、<code>x</code> と <code>y</code> の和を返します。</p> + +<p>要するに、<code>makeAdder</code> は関数ファクトリです。これは与えられた引数に特定の値を足す関数を作ります。上の例では関数ファクトリを使って 2 つの新しい関数を作成しています。1 つは引数に 5 を加えるもので、もう 1 つは 10 を加えるものです。</p> + +<p><code>add5</code> と <code>add10</code> は両方ともクロージャです。両者は同じ関数本体の定義を共有していますが、保有している環境は異なります。<code>add5</code> の環境では <code>x</code> は 5 で、<code>add10</code> の環境では <code>x</code> は 10 です。</p> + +<h2 id="Practical_closures" name="Practical_closures">実用的なクロージャ</h2> + +<p>理論はこれぐらいにしておくとして、クロージャは実際の役に立つのでしょうか? クロージャの実用上の意義を考えてみましょう。クロージャを使うと、データ (環境) をそれを操作する関数と結びつける事が出来ます。これはオブジェクトを使ってデータ (オブジェクトのプロパティ) を 1 つかそれ以上のメソッドと結びつける事が出来るオブジェクト指向プログラミングと明らかに類似しています。</p> + +<p>したがって、メソッドを 1 つだけ持つオブジェクトを使いたくなるような状況ならば、どんな時でもクロージャを使う事ができます。</p> + +<p>ウェブではこのような状況はよくあります。私たちが書く JavaScript のコードは大半がイベントベースです。つまり、ある動作を定義し、それを click や keypress といったユーザーによって引き起こされるイベントに取り付けます。私たちのコードの多くはコールバック、すなわちイベントに反応して実行される単独の関数として取り付けられます。</p> + +<p>実例を挙げましょう。あるページにそのページのテキストの大きさを調整するためのボタンを追加しようとしているとします。1 つの方法として、まず <code>body</code> 要素の font-size をピクセル数で指定して、そのページ内の (見出しなどの) 他の要素のサイズを相対単位 <code>em</code> で設定します。</p> + +<pre class="brush:css notranslate">body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.2em; +} +</pre> + +<p>これから作る対話式のテキストサイズ調整ボタンは、<code>body</code> 要素の <code>font-size</code> プロパティを変更し、その変更は相対単位によってページ上のほかの要素にも適用されます。</p> + +<p>JavaScript のコード:</p> + +<pre class="brush:js notranslate">function makeSizer(size) { + return function() { + document.body.style.fontSize = size + 'px'; + }; +} + +var size12 = makeSizer(12); +var size14 = makeSizer(14); +var size16 = makeSizer(16);</pre> + +<p><code>size12</code>、<code>size14</code>、<code>size16</code> はそれぞれ body のテキストサイズを 12、14、16 px に変更する関数になっています。これらは以下のようにしてボタン (この場合はリンク) に取り付けられます。</p> + +<pre class="brush:js notranslate">document.getElementById('size-12').onclick = size12; +document.getElementById('size-14').onclick = size14; +document.getElementById('size-16').onclick = size16;</pre> + +<pre class="brush:html notranslate"><a href="#" id="size-12">12</a> +<a href="#" id="size-14">14</a> +<a href="#" id="size-16">16</a> +</pre> + +<p><a href="https://jsfiddle.net/vnkuZ/7726/">JSFiddle</a> でコードを実行します。</p> + +<h2 id="Emulating_private_methods_with_closures" name="Emulating_private_methods_with_closures">クロージャでプライベートメソッドを模倣する</h2> + +<p>Java などの言語ではプライベートなメソッドを宣言することが出来ます。これは同じクラス内にあるほかのメソッドからのみ呼び出せるメソッドのことです。</p> + +<p>JavaScript にはこういった機能は組み込まれていませんが、クロージャを使うとプライベートメソッドを模倣する事ができます。プライベートメソッドはコードへのアクセスを制限するのに役立つだけではなく、コードのパブリックインターフェイスが不要なメソッドでいっぱいになるのを防ぐため、グローバル名前空間を管理するのに非常に有効です。</p> + +<p><a href="http://www.google.com/search?q=javascript+module+pattern" title="http://www.google.com/search?q=javascript+module+pattern">モジュールパターン</a>としても知られる、クロージャを使って、プライベートな関数と変数にアクセスできるパブリック関数を定義するにはこのようにします。</p> + +<pre class="brush: js notranslate">var counter = (function() { + var privateCounter = 0; + function changeBy(val) { + privateCounter += val; + } + + return { + increment: function() { + changeBy(1); + }, + + decrement: function() { + changeBy(-1); + }, + + value: function() { + return privateCounter; + } + }; +})(); + +console.log(counter.value()); // 0. + +counter.increment(); +counter.increment(); +console.log(counter.value()); // 2. + +counter.decrement(); +console.log(counter.value()); // 1. +</pre> + +<p>ここでは色々な事が行われています。前の例ではクロージャがそれぞれ独自の環境を持っていましたが、この例では環境が 1 つだけ作成され、その環境は <code>counter.increment</code>, <code>counter.decrement</code>, <code>counter.value</code> という 3 つの関数によって共有されています。</p> + +<p>この共有レキシカル環境は、<em>定義されるとすぐに実行される</em>(IIFE とも呼ばれます)無名関数の本文で作成されています。この環境は変数 <code>privateCounter</code> と関数 <code>changeBy</code> という 2 つのプライベートアイテムを含んでいます。これらはどちらも無名関数の外側からは直接アクセス出来ません。その代わり、この無名ラッパ関数から返される 3 つのパブリック関数からはアクセスできます。</p> + +<p>これら 3 つのパブリック関数は同じ環境を共有するクロージャです。JavaScript のレキシカルスコープにより、これらの関数はそれぞれが変数 <code>privateCounter</code> と関数 <code>changeBy</code> にアクセスできます。</p> + +<div class="note"> +<p>カウンターを作成する無名関数を定義して、それをすぐに呼び出して結果を <code>counter</code> 変数に代入していることに注目してください。この関数を別の変数 <code>makeCounter</code> に格納し、複数のカウンターを作成するために使用することができます。</p> +</div> + +<pre class="brush: js notranslate">var makeCounter = function() { + var privateCounter = 0; + function changeBy(val) { + privateCounter += val; + } + return { + increment: function() { + changeBy(1); + }, + + decrement: function() { + changeBy(-1); + }, + + value: function() { + return privateCounter; + } + } +}; + +var counter1 = makeCounter(); +var counter2 = makeCounter(); + +alert(counter1.value()); // 0. + +counter1.increment(); +counter1.increment(); +alert(counter1.value()); // 2. + +counter1.decrement(); +alert(counter1.value()); // 1. +alert(counter2.value()); // 0. +</pre> + +<p>2 つのカウンターが互いに独立した状態を維持していることに注目してください。各クロージャは、独自のクロージャを介して異なるバージョンの <code>privateCounter</code> 変数を参照しています。カウンターのいずれかが呼び出されるたびに、この変数の値を変更することで、そのレキシカル環境が変化します。あるクロージャで変数値を変更しても、もう一方のクロージャの値には影響しません。</p> + +<div class="note"> +<p>このようにしてクロージャを使うと、普通はオブジェクト指向プログラミングに付き物のいくつかの利点、具体的には<em>データの隠蔽</em>や<em>カプセル化</em>が利用できるようになります。</p> +</div> + +<h2 id="Closure_Scope_Chain" name="Closure_Scope_Chain">クロージャのスコープチェーン</h2> + +<p>すべてのクロージャには 3 つのスコープがあります。</p> + +<ul> + <li>ローカルスコープ(独自のスコープ)</li> + <li>外側の関数スコープ</li> + <li>グローバルスコープ</li> +</ul> + +<p>よくある間違いは、外側の関数がネストされた関数である場合、外側の関数スコープへのアクセスにはさらに外側の関数スコープが含まれており。実質的にスコープチェーンが生じていることに気づかないことです。次のコードの例を考えてみましょう。</p> + +<pre class="brush: js notranslate">// グローバルスコープ +var e = 10; +function sum(a){ + return function(b){ + return function(c){ + // ローカルスコープ + return function(d){ + // ローカルスコープ + return a + b + c + d + e; + } + } + } +} + +console.log(sum(1)(2)(3)(4)); // log 20 + +// 無名関数を使わずに書くこともできます。 + +// グローバルスコープ +var e = 10; +function sum(a){ + return function sum2(b){ + return function sum3(c){ + // 外側の関数スコープ + return function sum4(d){ + // ローカルスコープ + return a + b + c + d + e; + } + } + } +} + +var s = sum(1); +var s1 = s(2); +var s2 = s1(3); +var s3 = s2(4); +console.log(s3) //log 20 +</pre> + +<p>上記の例では、一連のネストされた関数があり、それらはすべて外側の関数スコープにアクセスできます。この文脈では、クロージャは<em>すべて</em>の外側の関数スコープにアクセスできると言えます。</p> + +<h2 id="Creating_closures_in_loops_A_common_mistake" name="Creating_closures_in_loops_A_common_mistake">よくある間違い: ループ内でクロージャを作成する</h2> + +<p>ECMAScript 2015 で <a href="/ja/docs/Web/JavaScript/Reference/Statements/let" title="let"><code>let</code></a> キーワードが導入される前までは、ループの内部でクロージャが作成された時にある問題がよく起こっていました。次のような例を考えてみます。</p> + +<pre class="brush:html notranslate"><p id="help">ここにヘルプが表示されます</p> +<p>E メール: <input type="text" id="email" name="email"></p> +<p>名前: <input type="text" id="name" name="name"></p> +<p>年齢: <input type="text" id="age" name="age"></p> +</pre> + +<pre class="brush: js notranslate">function showHelp(help) { + document.getElementById('help').innerHTML = help; +} + +function setupHelp() { + var helpText = [ + {'id': 'email', 'help': 'あなたの E メールアドレス'}, + {'id': 'name', 'help': 'あなたのフルネーム'}, + {'id': 'age', 'help': 'あなたの年齢 (17 歳以上)'} + ]; + + for (var i = 0; i < helpText.length; i++) { + var item = helpText[i]; + document.getElementById(item.id).onfocus = function() { + showHelp(item.help); + } + } +} + +setupHelp(); +</pre> + +<p><a href="https://jsfiddle.net/v7gjv/8164/">JSFiddle</a> でコードを実行します。</p> + +<p>配列 <code>helpText</code> は 3 つのヘルプを定義しており、それぞれがドキュメント内の入力フィールドの ID と関連付けられています。ループがこれらの定義を巡回して、それぞれの入力フィールドの onfocus イベントをそれに関連付けられたヘルプを表示するメソッドと結び付けています。</p> + +<p>このコードを実行してみると、期待したとおりには動かないのが判ります。どのフィールドにフォーカスしても、表示されるのは年齢についてのメッセージです。</p> + +<p>こうなる理由は、<code>onfocus</code> に代入された関数がクロージャだからです。このクロージャは、関数定義と、<code>setupHelp</code> 関数スコープから捕捉された環境から成っています。クロージャは 3 つ作られましたが、これらはみな 1 つの同じ環境を共有しています。<code>onfocus</code> コールバックが実行される時にはループはすべて終了しており、変数 item (3 つのクロージャ全てに共有されている) は <code>helpText</code> リストの最後の項目を示したままにされています。</p> + +<p>こういった場合の解決策の 1 つとして、より多くのクロージャを使う方法があります。具体的には、前に述べたような関数ファクトリを使います。</p> + +<pre class="brush:js notranslate">function showHelp(help) { + document.getElementById('help').innerHTML = help; +} + +function makeHelpCallback(help) { + return function() { + showHelp(help); + }; +} + +function setupHelp() { + var helpText = [ + {'id': 'email', 'help': 'メールアドレス'}, + {'id': 'name', 'help': '氏名'}, + {'id': 'age', 'help': '年齢 (17歳以上)'} + ]; + + for (var i = 0; i < helpText.length; i++) { + var item = helpText[i]; + document.getElementById(item.id).onfocus = makeHelpCallback(item.help); + } +} + +setupHelp(); +</pre> + +<p><a href="https://jsfiddle.net/v7gjv/9573/">JSFiddle</a> でコードを実行します。</p> + +<p>これは期待通り動きます。全てのコールバックが 1 つの環境を共有するのではなく、<code>makeHelpCallback</code> 関数がそれぞれに対して<em>新しいレキシカル環境</em>を作っており、そこでは <code>help</code> が配列 <code>helpText</code> の対応する文字列を参照しています。</p> + +<p>上のものを匿名クロージャを使って書く、他の方法もあります:</p> + +<pre class="brush: js notranslate">function showHelp(help) { + document.getElementById('help').innerHTML = help; +} + +function setupHelp() { + var helpText = [ + {'id': 'email', 'help': 'メールアドレス'}, + {'id': 'name', 'help': '氏名'}, + {'id': 'age', 'help': '年齢 (17歳以上)'} + ]; + + for (var i = 0; i < helpText.length; i++) { + (function() { + var item = helpText[i]; + document.getElementById(item.id).onfocus = function() { + showHelp(item.help); + } + })(); // Immediate event listener attachment with the current value of item (preserved until iteration). + } +} + +setupHelp();</pre> + +<p>これ以上クロージャを使いたくない場合、ES2015 で導入された <code><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/let">let</a></code> キーワードも使用できます:</p> + +<pre class="brush: js notranslate">function showHelp(help) { + document.getElementById('help').innerHTML = help; +} + +function setupHelp() { + var helpText = [ + {'id': 'email', 'help': 'メールアドレス'}, + {'id': 'name', 'help': '氏名'}, + {'id': 'age', 'help': '年齢 (17歳以上)'} + ]; + + for (var i = 0; i < helpText.length; i++) { + let item = helpText[i]; + document.getElementById(item.id).onfocus = function() { + showHelp(item.help); + } + } +} + +setupHelp();</pre> + +<p>この例では <code>var</code> の代わりに <code>let</code> を使っているため、すべてのクロージャがブロックスコープの変数をバインドしており、つまり追加のクロージャは要求されません。</p> + +<p>他の方法として、以下のように <code>forEach()</code> を使用して配列 <code>helpText</code> を操作し、それぞれの {{htmlelement("div")}} にリスナーを割り当てることができます。</p> + +<pre class="brush: js notranslate">function showHelp(help) { + document.getElementById('help').innerHTML = help; +} + +function setupHelp() { + var helpText = [ + {'id': 'email', 'help': 'メールアドレス'}, + {'id': 'name', 'help': '氏名'}, + {'id': 'age', 'help': '年齢 (17歳以上)'} + ]; + + helpText.forEach(function(text) { + document.getElementById(text.id).onfocus = function() { + showHelp(text.help); + } + }); +} + +setupHelp();</pre> + +<h2 id="Performance_considerations" name="Performance_considerations">パフォーマンスへの配慮</h2> + +<p>あるタスクを実行する時、クロージャが必要とされていないのにいたずらに関数を他の関数の中に作成するのは、スクリプトのパフォーマンスに悪影響を及ぼすのであまり賢いやり方ではありません。</p> + +<p>例えば、新しくオブジェクト/クラスを作成する時、一般的にメソッドはオブジェクトのコンストラクタの中で定義するのではなく、オブジェクトのプロトタイプに結びつけるべきです。コンストラクタの中で定義してしまうと、コンストラクタが呼び出されるたびに (つまりオブジェクトが作成されるたびに) メソッドが再代入されてしまうことになるからです。</p> + +<p>次の実践的ではない例証のためのケースを考えます。</p> + +<pre class="brush: js notranslate">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); + this.getName = function() { + return this.name; + }; + + this.getMessage = function() { + return this.message; + }; +} +</pre> + +<p>上のコードではクロージャを使う事によって得るものが何もないので、再構成するべきです。</p> + +<pre class="brush: js notranslate">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); +} +MyObject.prototype = { + getName: function() { + return this.name; + }, + getMessage: function() { + return this.message; + } +}; +</pre> + +<p>ただし、プロトタイプの再定義は推奨されません。以下の例のように既存のプロトタイプに追加するのがより好ましい方法です。</p> + +<pre class="brush: js notranslate">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); +} +MyObject.prototype.getName = function() { + return this.name; +}; +MyObject.prototype.getMessage = function() { + return this.message; +}; +</pre> + +<p>上の 2 つの例では、プロトタイプが継承されて全てのオブジェクトによって共有されるため、オブジェクトが作成されるたびにメソッドが定義されずに済みます。詳しくは<a href="/ja/docs/Web/JavaScript/Guide/Details_of_the_Object_Model">オブジェクトモデルの詳細</a> を参照して下さい。</p> |