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

スポンサーサイト

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

匿名関数やクロージャで出くわすC#とJavaScriptでのスコープの違い

                
tags: C# JavaScript
クロージャについて調べているうちに、C#とJavaScriptのスコープの違いに出くわした。
C#はブロック(ブレース"{}"で区切られたコードの単位)ごとにスコープが変わることを初めて知った。さすがにメソッドのブロックに関しては問題なかったけど、ifやforでもブレースによってブロック区切りが発生していることは知らなかった。C#で以下は不可。
for (var x=0; x<10; x++)
{
var hoge = x;
}
Console.WriteLine(hoge); // can't use hoge


JavaScriptなら以下は可能。
for (var x=0; x<10; x++)
{
var hoge = x;
}
alert(hoge);


以上のことが、クロージャを使う上で差異をもたらす。


ボタンを6つ作ってGUIとして表示したいとする。ボタンをクリックするとそれぞれ、0から5の数字を出力するようにする。これをC#とJavaScriptでやる。

C# with WPFだと以下のように書ける。
for (var x=0; x<6; x++)
{
Button button = new Button();
button.Content = "button" + x.ToString();
var xClone = x;
button.Click += delegate(Object sender, RoutedEventArgs e)
{
var innerString = xClone.ToString();
Console.WriteLine(innerString);
};

stackPanel.Children.Add(button);
}


JavaScriptで上記のC#のコードをまねると、以下のように書ける。でもこれじゃ狙い通りに動かない。どのボタンをクリックしても5が表示されるようになる。
for (var x=0; x<6; x++)
{
var button = document.createElement('button');
button.type= 'button';
button.appendChild(document.createTextNode('button' + x));
var xClone = x;
button.onclick = function()
{
alert(xClone);
};

window.document.body.appendChild(button);
}


ボタンクリックで常に5が表示されてしまうのは、クロージャ内のxCloneがどのボタンクリックでも常に、現在のxCloneの値を同一に参照しているということだ。C#と違ってスコープがブロックレベルでないことに起因している。

ではでは解決法は……関数を二重に定義して、内部関数への参照を返す形でイベントに渡すクロージャの定義をする。その外部関数のなかでxCloneを定義してやってはどうか。
xCloneを外部関数内で定義すれば、その上位スコープからは見えず、下位スコープでありクロージャとしてイベントに渡す内部関数からなら見える。ループをするたびに異なるスコープでxCloneが生まれ、それはイベントに渡された各クロージャからアクセスが可能だ。

for (var x=0; x<6; x++)
{
var button = document.createElement('button');
button.type= 'button';
button.appendChild(document.createTextNode('button' + x));
button.onclick = (function()
{
var xClone = x;
return function()
{
alert(xClone);
}
})();

// 引数を使う別解
// button.onclick = (function(xClone)
// {
// return function()
// {
// alert(xClone);
// }
// })(x);

window.document.body.appendChild(button);
}


Mozillaのサイトでもまた別の書き方で問題をクリアしていた。


参考
https://developer.mozilla.org/ja/docs/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

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

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