主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

クロージャの入門を越えた正しい理解

                
"大げさに考えすぎないよう、気をつけることだよ"
- スナフキン




いくらかクロージャについて書いてきたが、今一度ここでまとめる。ほかのブログでクロージャの記事を読むと、間違ってはいないのだけど、クロージャというのを狭く理解しているのが結構あった。それでは利用箇所や方法を狭めてしまう。
この記事はなにがクロージャというかを確認するために書いた。基礎知識の説明は多少はしょっている。難しいと思ったらgihyoで書かれた記事で入門がおすすめ。


C#でクロージャってどう書いてるのかと調べたところ、あるブログで「匿名メソッドはクロージャでない」とタイトルで言っているのに、後日編集で記事の冒頭に「匿名メソッドはまさにレキシカルクロージャ(クロージャの別名)でした」と書いてあるのを見つけた。これを読んで、クロージャって具体的になんなのかを考え直してみた。
http://blogs.msdn.com/b/abhinaba/archive/2005/10/18/482180.aspx

別のブログのクロージャ入門の記事をいくつか読んでいると、クロージャを作るために必要なものとして、
・関数内の関数
・関数を返す関数
・無名関数
以上のものが必要と書いてあった。……関数内の関数以外は条件としていらない。
関数を返す関数も無名関数も使わずにクロージャを書く。関数foo内の一連がクロージャである。
function foo()
{
var x = 'local variable of function "foo", and not global variable';
function clsr()
{
alert(x);
}
}


関数の中にある関数が、引数でもローカル変数でもグローバル変数でもない変数、自分の上位にある関数のローカル変数を使っていたとする。JavaScriptではレキシカルスコープが有効なのでそういうことができる。そんな関数があったら、中の関数が定義されている一連の環境がクロージャだ。重ねるが、関数を返す関数とか無名関数を使わないといけないとかは大げさなことだ。

Wikipediaでは――Wikipediaをソースにするのはいいことではないが、そこから出典をたどっても完全なソースは見つからなかった――クロージャは、
"引数以外の変数を実行時の環境ではなく、自身が定義された環境(レキシカルスコープ)において解決することを特徴とする"
とされている。英語版では、
"In computer science, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables) of that function."
―コンピュータサイエンスにおいてクロージャ(あるいはレキシカルクロージャ、関数クロージャ)は、自由変数への参照環境を含む、関数もしくは関数への参照である
とされていて、意味することは同じだ。だから世界でこれがクロージャの通例とされているとみていいだろう。

英語版ではクロージャについて「関数もしくは関数への参照である」と書いてある。ここから読み取れるのは、定義した関数がそのままクロージャになっていることがあるということ。関数を返す関数は必須条件ではない。そして無銘関数やラムダを使わないといけないとも書いていない。それらはあくまで、クロージャを作成するときにあると便利な機能といったところだ。
クロージャとしての必須条件は、クロージャとなるその関数が変数をローカルだけでなく、レキシカルスコープ内を参照して解決していることである。上のスクリプトでは変数xは関数clsr内にない。だけど関数clsrのスコープは上位の関数にも有効であるから、関数fooのなかを参照して変数xを解決する。これがクロージャである。

さて上に書いたスクリプトは説明のためだけに書いたもので、こんなものどう使うのかと。ちょっと付け足して、実用的な感じに。
function foo()
{
var x = 'local variable of function "foo", and not global variable';
function clsr()
{
alert(x);
}

var image = new Image();
image.onload = clsr;
image.src = "any image sooooooooooooooooooooource!";
}


無銘関数を使うとシンプルに書き換えられる。image.onloadに渡されているのが無名関数で作ったクロージャ。
function foo()
{
var x = 'local variable of function "foo", and not global variable';
var image = new Image();
image.onload = (function(){alert(x);});
image.src = "any image sooooooooooooooooooooource!";
}




クロージャを使うメリットは、グローバル変数を使わずに値を保持できるところだという。それだけ?
クロージャを使うと、関数に値を渡したいときに引数やグローバル変数を使わなくてもできるようになる。なぜそんなことができるかっていうのがまさにクロージャ。ある関数みずからの内で定義されていない変数があっても、レキシカルスコープ内でそれを解決できるから。それを理解していると下記のスクリプトはまた違った書き方ができる。
4つのボタンに、それぞれのテキストのスタイルを変更するクリックイベントを付加する。
1305152.html

<body>
<button id="b1">button1</button>
<button id="b2">button2</button>
<button id="b3">button3</button>
<button id="b4">button4</button><br><br>
<span id="str1">I will be smaller.</span><br><br>
<span id="str2">I will be smaller, and colored.</span><br><br>
<span id="str3">I will be smaller, colored, and bold.</span><br><br>
<span id="str4">I will be smaller, colored, bold, and Italic.</span><br><br>
<script>
function b1Clicked(size)
{
return function(){$("#str1").css("font-size", size);};
}

function b2Clicked(size, color)
{
return function(){$("#str2").css("font-size", size)
.css("color", color);};
}

function b3Clicked(size, color, weight)
{
return function(){$("#str3").css("font-size", size)
.css("color", color)
.css("font-weight", weight);};
}

function b4Clicked(size, color, weight, style)
{
return function(){$("#str4").css("font-size", size)
.css("color", color)
.css("font-weight", weight)
.css("font-style", style);};
}

function onLoad()
{
var size = "15px",
color = "#00f",
weight = "bold",
style = "italic";

$("#b1").click(b1Clicked(size));

$("#b2").click(b2Clicked(size, color));

$("#b3").click(b3Clicked(size, color, weight));

$("#b4").click(b4Clicked(size, color, weight, style));
}

window.onload = onLoad;
</script>


</body>

上記のスクリプトでは引数を共通の変数から持ってきているため、スタイルの一括変更が容易になっている。グローバルな関数はonLoadと、クリックイベントで呼ばれる四つを合わせて計五つが定義された。

上記のスクリプトを、レキシカルスコープを参照するというクロージャの特性を使って書き直す。グローバルな関数がonLoadのみにまとめられる。でもクリックイベントは上記のスクリプトと同様に動作する。
1305151.html
<body>
<button id="b1">button1</button>
<button id="b2">button2</button>
<button id="b3">button3</button>
<button id="b4">button4</button><br><br>
<span id="str1">I will be smaller.</span><br><br>
<span id="str2">I will be smaller, and colored.</span><br><br>
<span id="str3">I will be smaller, colored, and bold.</span><br><br>
<span id="str4">I will be smaller, colored, bold, and Italic.</span><br><br>
<script>
function onLoad()
{
var color = "#00f";
var size = "15px";
var weight = "bold";
var style = "italic";

$("#b1").click(function(){$("#str1").css("font-size", size);});

$("#b2").click(function(){$("#str2").css("font-size", size)
.css("color", color);
});

$("#b3").click(function(){$("#str3").css("font-size", size)
.css("color", color)
.css("font-weight", weight);
});

$("#b4").click(function(){$("#str4").css("font-size", size)
.css("color", color)
.css("font-weight", weight)
.css("font-style", style);
});
}

window.onload = onLoad;
</script>
</body>



最後に。上記のようなクロージャの使い方をしたときに注意をすべきことがある。レキシカルスコープをたどって解決される変数は、変数への参照をしているのであり、それは遅延評価である。簡単に言えば関数定義時の値で固定されるわけではない。定義後から実行までに被参照側でその値が変われば、その変化が参照側の変数にも表れる。
言葉ではわかりづらいだろうからスクリプトで。上記のスクリプトの関数onLoadの最後に一行だけ足してみる。
1305153.html
<body>
<button id="b1">button1</button>
<button id="b2">button2</button>
<button id="b3">button3</button>
<button id="b4">button4</button><br><br>
<span id="str1">I will be smaller.</span><br><br>
<span id="str2">I will be smaller, and colored.</span><br><br>
<span id="str3">I will be smaller, colored, and bold.</span><br><br>
<span id="str4">I will be smaller, colored, bold, and Italic.</span><br><br>
<script>
function onLoad()
{
var color = "#00f";
var size = "15px";
var weight = "bold";
var style = "italic";

$("#b1").click(function(){$("#str1").css("font-size", size);});

$("#b2").click(function(){$("#str2").css("font-size", size)
.css("color", color);
});

$("#b3").click(function(){$("#str3").css("font-size", size)
.css("color", color)
.css("font-weight", weight);
});

$("#b4").click(function(){$("#str4").css("font-size", size)
.css("color", color)
.css("font-weight", weight)
.css("font-style", style);
});

color = "#999";
}

window.onload = onLoad;
</script>
</body>

ボタンクリックによって文字は何色になっただろう? colorが定義時のままなら青だが、のちにcolorにグレーの値が入れられて関数onLoadが終了する。このあとにボタンクリックが発生すれば、当然ながらcolorはグレーの値である。
というわけで被参照側の変数の値の変化に気を払わなければならないというのがクロージャ作成の留意点。じゃあ変化をどう回避するかっていうと、定義時に実行する関数を一段加えて、その関数内で変数のクローンをつくればいい。
<body>
<button id="b1">button1</button>
<button id="b2">button2</button>
<button id="b3">button3</button>
<button id="b4">button4</button><br><br>
<span id="str1">I will be smaller.</span><br><br>
<span id="str2">I will be smaller, and colored.</span><br><br>
<span id="str3">I will be smaller, colored, and bold.</span><br><br>
<span id="str4">I will be smaller, colored, bold, and Italic.</span><br><br>
<script>
function onLoad()
{
var color = "#00f";
var size = "15px";
var weight = "bold";
var style = "italic";

$("#b1").click(function(){$("#str1").css("font-size", size);});

(function()
{
var colorClone = color;
$("#b2").click(function(){$("#str2").css("font-size", size)
.css("color", colorClone);
});


$("#b3").click(function(){$("#str3").css("font-size", size)
.css("color", colorClone)
.css("font-weight", weight);
});

$("#b4").click(function(){$("#str4").css("font-size", size)
.css("color", colorClone)
.css("font-weight", weight)
.css("font-style", style);
});
})();

color = "#999";
}

window.onload = onLoad;
</script>
</body>
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。