2009.10.19 Monday
JavaScript: The Good Parts を読みましたところ、いろいろと勉強になることがありました。
付録を除くと100ページちょいという、とてもライトな本ですが、内容は濃密です。JavaScript の中で、一番わかりにくいだろうなと思われるようなところが集中的に解説されているように感じられ、勉強したてでかつそろそろわかりかけてきたなーと思い始めていた僕にとって、最適の一冊でした。知って良かったなと思えるところがたくさんありましたので、これから少しずつ紹介して行きたいと思いますが、今日はその中でも特に印象的だった、変数のスコープにまつわるお話をしてみたいと思います。
変数のスコープはその変数が宣言された関数の中だけに限定されます。なんとなく知ってはいたのですが、for 文とかではこういう風に書いてしまうことが多々ありました。
これを踏まえた問題をひとつ。これも、この本からの引用なので恐縮です。
下記のようなhtml が与えられたときに、h1 の「JavaScript」 をクリックしたら「1」を、h2 の「The Good Parts」 をクリックしたら「2」をalert するというような、そのnode が何番目のnode なのかをalert するというコードを考えるとします。
0番目のnode: body タグの直後の改行(テキストノード)
1番目のnode: h1
2番目のnode: h2
3番目のnode: pre
4番目のnode: pre タグの直後の改行(テキストノード)
program.js
これは間違いの例です。僕はパッと見ただけではこのコードの問題点がわかりませんでした。
実は、上記のjs では、どちらをクリックしても4 がalert されてしまいます。4 というのはノードの総数です。
これはクロージャの概念と変数のスコープがわかっていればわかることらしいので、本では詳細が書かれておりませんでした。そこで、僕なりの解釈を付け加えてみたいと思います。このfor 文のi=1 の時には、「1番目のnode(つまりh1) のonclick イベントに function() { alert(i); } を割り当てろ」という命令がなされています。これで、h1 がonclick イベントに反応する準備ができました。そして、実際に、h1 がクリックされると、その際に実行されるのは、alert(i) です。では、このalert(i) が実行されたときに、i の値はどうなっているでしょうか。onclick を割り当てたタイミングでは1 だったかもしれないi の値ですが、実際にはにfor 文がnodes の総数分実行されたあとなので、i = 4 になってしまっています。その結果、alert(i) で、「4」がalert されてしまうということになります。
では、どのように直せばよいのでしょうか。
このように、「関数を返す無名関数をすぐに実行する」という少しわかりづらいコードで、問題を回避できます。先ほどと同じように、for 文のi=1 の時に何が起きているか見てみたいと思います。i=1 の時には、「h1 のonclick イベントに、function(i) { // [function を返すコード] }(i) の実行結果(戻り値が関数)を割り当てろ」という命令がなされています。具体的に言うと、このfunction(i){ }(i) はこのタイミングで直ちに実行されますので、戻ってくる関数は「funcion() { alert(1); }」となります(i=1 なのでalert の中身が1 になる)。つまり、for 文のi=1 の時には、「h1 のonclick イベントにfunction() { alert(1); } を割り当てろ」という命令がなされていることになります。i=1 以外の時も同様で、onclick イベントに割り当てるタイミング(つまりfor 文がまわってるとき)で、その時点でのi を処理してしまっていることが最大のポイントです。その結果、実際にh1 のnode がクリックされると、今度は正しくalert(1) が実行されるようになります。
言葉で書くと返ってわかりづらく見えてしまいましたが、そういうことです。
この、無名関数を定義して、すぐに実行するという、function(){}(); というのは結構便利なので、覚えておくと良いかもしれません。
![]() | Douglas Crockford, 水野 貴明 ¥ 1,890 ![]() 手っ取り早く習得 痒いとこだけ掻いてくれる JavaScriptを勉強しなおすのにとってもよいです。 JavaScript コアに関する最高の本 |
付録を除くと100ページちょいという、とてもライトな本ですが、内容は濃密です。JavaScript の中で、一番わかりにくいだろうなと思われるようなところが集中的に解説されているように感じられ、勉強したてでかつそろそろわかりかけてきたなーと思い始めていた僕にとって、最適の一冊でした。知って良かったなと思えるところがたくさんありましたので、これから少しずつ紹介して行きたいと思いますが、今日はその中でも特に印象的だった、変数のスコープにまつわるお話をしてみたいと思います。
変数のスコープはその変数が宣言された関数の中だけに限定されます。なんとなく知ってはいたのですが、for 文とかではこういう風に書いてしまうことが多々ありました。
for (var i=0; i<array.length; i++) {
// alert('test: ' + i);
}ブロックレベルのスコープはないので、これは下記と同じことなんですね。var i;変数宣言はそのスコープをできるだけ狭める方がお行儀が良いので、他のプログラミング言語ではなるべく内側のブロック、例えばさっきの例ではfor 文のブロック内で宣言するのがよいとされているのですが、JavaScript では、関数内の先頭に書くのが一番わかりやすくなります。そうすれば、その関数全体がスコープになるというのが直感的にわかるようになると思います。
for (i=0; i<array.length; i++) {
// alert('test: ' + i);
}
これを踏まえた問題をひとつ。これも、この本からの引用なので恐縮です。
下記のようなhtml が与えられたときに、h1 の「JavaScript」 をクリックしたら「1」を、h2 の「The Good Parts」 をクリックしたら「2」をalert するというような、そのnode が何番目のnode なのかをalert するというコードを考えるとします。
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">【参考】
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="good_parts.css" rel="stylesheet" type="text/css" />
<title>JavaScript The Good Parts</title>
</head>
<body>
<h1>JavaScript</h1><h2>The Good Parts</h2><pre><script src="program.js"></script></pre>
</body>
</html>
0番目のnode: body タグの直後の改行(テキストノード)
1番目のnode: h1
2番目のnode: h2
3番目のnode: pre
4番目のnode: pre タグの直後の改行(テキストノード)
program.js
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function () {
alert(i);
}
}
};
add_the_handlers(document.body.childNodes);これは間違いの例です。僕はパッと見ただけではこのコードの問題点がわかりませんでした。
実は、上記のjs では、どちらをクリックしても4 がalert されてしまいます。4 というのはノードの総数です。
これはクロージャの概念と変数のスコープがわかっていればわかることらしいので、本では詳細が書かれておりませんでした。そこで、僕なりの解釈を付け加えてみたいと思います。このfor 文のi=1 の時には、「1番目のnode(つまりh1) のonclick イベントに function() { alert(i); } を割り当てろ」という命令がなされています。これで、h1 がonclick イベントに反応する準備ができました。そして、実際に、h1 がクリックされると、その際に実行されるのは、alert(i) です。では、このalert(i) が実行されたときに、i の値はどうなっているでしょうか。onclick を割り当てたタイミングでは1 だったかもしれないi の値ですが、実際にはにfor 文がnodes の総数分実行されたあとなので、i = 4 になってしまっています。その結果、alert(i) で、「4」がalert されてしまうということになります。
では、どのように直せばよいのでしょうか。
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function () {
alert(i);
}
}(i);
}
};
add_the_handlers(document.body.childNodes);このように、「関数を返す無名関数をすぐに実行する」という少しわかりづらいコードで、問題を回避できます。先ほどと同じように、for 文のi=1 の時に何が起きているか見てみたいと思います。i=1 の時には、「h1 のonclick イベントに、function(i) { // [function を返すコード] }(i) の実行結果(戻り値が関数)を割り当てろ」という命令がなされています。具体的に言うと、このfunction(i){ }(i) はこのタイミングで直ちに実行されますので、戻ってくる関数は「funcion() { alert(1); }」となります(i=1 なのでalert の中身が1 になる)。つまり、for 文のi=1 の時には、「h1 のonclick イベントにfunction() { alert(1); } を割り当てろ」という命令がなされていることになります。i=1 以外の時も同様で、onclick イベントに割り当てるタイミング(つまりfor 文がまわってるとき)で、その時点でのi を処理してしまっていることが最大のポイントです。その結果、実際にh1 のnode がクリックされると、今度は正しくalert(1) が実行されるようになります。
言葉で書くと返ってわかりづらく見えてしまいましたが、そういうことです。
この、無名関数を定義して、すぐに実行するという、function(){}(); というのは結構便利なので、覚えておくと良いかもしれません。

