どうも!かけちまるです!
JavaScriptやjQueryでfor文を使い、そのfor文内にsetTimeoutとfor文で定義した変数を使って処理を遅らせるようなコードを書いたときに思ったように動かなかったので解決策をメモしていきます。
この問題を解決するには、スコープとクロージャについて理解する必要があります。
この記事では、
を解説します。
今回やりたいこととしては、for
文を使ってConsole
に1秒ごと時間差で1~10を表示したいとします。
実装成功するとこんな感じになります。
JavaScriptコピーfunction count(){ for (var i=1; i <= 10; i += 1){ setTimeout(function() { console.log(i); }, i*1000); } } count();
JavaScriptコピーfunction count(){ for(var i=1; i <= 10; i += 1){ (function(i){ //追加 setTimeout(function() { console.log(i); }, i*1000); })(i); //追加 } } count();
スコープとは、影響範囲のこと。
例えば、関数「red」の中で変数「apple」を定義します。
一方で関数「blue」を定義します。
変数「apple」は、関数「red」の中では使うことができます。
しかし、関数「blue」の中では使えないのです。
つまり、変数「apple」は関数「red」の中でしか影響されません。
これがスコープの仕組みです。
JavaScriptコピーfunction red(){ var apple = 'りんご'; //変数appleを定義 } function blue(){ console.log(apple); //コンソールに変数appleを出力 } blue(); //結果:ReferenceErrorエラーになる
プログラミング言語によってスコープの規則はそれぞれ異なります。
JavaScriptでは関数レベルとブロックレベルのレキシカルスコープを採用しています。
関数レベルのスコープは文字通り関数内で有効ということになります。
JavaScriptでvar
を使って宣言した変数は、関数レベルのスコープを持ちます。
例えば以下のコードであればConsole
にredが問題なく出力されます。
JavaScriptコピーfunction name(){ if (true) { var color = 'red'; } console.log(color); // red } name();
もしvar
を使って宣言した変数がブロックレベルのスコープであれば変数var color
はif
文内でしか出力できません。
試しにvar
部分をconst
やlet
に変えてみるとそれがわかります。
JavaScriptでは、var
の他にconst
やlet
でも変数を作成できます。const
やlet
で宣言した変数は、ブロックレベルのスコープ変数を作成します。
JavaScriptコピーfunction name(){ if (true) { let color = 'red'; console.log(color); // red } console.log(color); // Uncaught ReferenceError: color is not defined } name();
let
で宣言した変数はブロックレベルのスコープになります。let color
をif
文内で宣言しているのでif
文内でしか許容できないということになります。if
文外でlet color
を使用した場合はエラーになります。
クロージャとは?と一言で説明するのが難しかったのでまず覚えておいてもらいたいキーワードがあります。
「JavaScriptの関数は全てクロージャになり得る」
ということです。
クロージャになり得るということは関数にある条件が加わるとその関数はクロージャになるということがわかります。
では、今回の問題だったコードを例に説明します。
改めて、問題だったのはfor文を使ってConsoleに1秒ごと時間差で1~10を表示したいけど思い通りにならなっかたということ。
で、うまくいかない例が以下です。
JavaScriptコピーfunction count(){ for (var i=1; i <= 10; i += 1){ setTimeout(function() { console.log(i); }, i*1000); } } count();
これだとConsole
に「11」が10回出力されてしまいます。setTimeout
は1秒後に変数iを上位スコープの関数count()
内から探して実行します。
しかし、最初の1秒間に変数iはすでに「11」になってしましいます。
そのため、1秒ごとに「11」が出力されてしまうということになってしまいます。
じゃあ期待通の結果にしたい時はどう知ればいいかというと、
の2つの方法があります。
JavaScriptコピーfunction count(){ for(var i=1; i <= 10; i += 1){ (function(i){ setTimeout(function() { console.log(i); }, i*1000); })(i); } } count();
JavaScriptコピーfunction count(){ for(let i=1; i <= 10; i += 1){ setTimeout(function() { console.log(i); }, i*1000); } } count();
おわり
フィードバックを送信
記事についてのフィードバックはTwitterかお問い合わせフォームから受け付けております。