質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

Q&A

解決済

6回答

10013閲覧

for文における、letとvarの挙動の違い

YESYUKI17

総合スコア28

CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

1グッド

11クリップ

投稿2019/08/13 02:49

編集2019/08/14 05:56

前提・実現したいこと

お世話になります。
JavaScriptで簡単なアイコンのアニメーションを設定したくてfor文で条件分岐で実現しようとしました。
以下のコードでfor文の条件をvarで設定するとうまくいかず、letで書くとうまくいくことまでわかりました。
なぜvarだとエラーが起こるのか根本的な理由を理解したいです。一応var,letの違いは認識しているつもりなのですが、ブロックスコープを認識するletでエラーがでず、認識しないvarでエラーになる理由がわかりません。
アドバイスいただけますと、大変助かります。

JavaScript

1'use strict'; 2{ 3 4 const plus = document.querySelectorAll('.plus'); 5 // 必要ない部分と変数名を変更しました 6 for (var i = 0; i < plus.length; i++) { 7 console.log(i) 8 // 期待した通り012と表示 9 plus[i].addEventListener('click', () => { 10 console.log(i); 11 // なぜ3が表示されるのか?i<plus.lengthは3未満の数値になるはず 12 if (plus[i].textContent === '+') { 13 plus[i].textContent = '-'; 14 } else { 15 plus[i].textContent = '+'; 16 } 17 }); 18 } 19 20 21} 22

HTML

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <title>Practice</title> 6 <link rel="stylesheet" href="css/styles.css"> 7 </head> 8 <body> 9 10 <p class="plus">+</p> 11 <p class="plus">+</p> 12 <p class="plus">+</p> 13 <!-- id削除しました --> 14 15 <script src="js/main.js"></script> 16 17 </body> 18</html> 19

CSS

1/* クラスに変更しました */ 2.plus { 3 color: #00AED9; 4 text-align: center; 5 line-height: 15px; 6 border: solid #DADADA 2px; 7 border-radius: 50%; 8 width: 20px; 9 height: 20px; 10 font-weight: bold; 11 font-size: 20px; 12}

補足情報(FW/ツールのバージョンなど)

Atomエディタ、CHROME

nullbot👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/08/14 06:29

> なぜ3が表示されるのか?i<plus.lengthは3未満の数値になるはず for (var i = 0; i < plus.length; i++) で plus.length は 3 なので、i は 3 までインクレメントされてからループを抜け、匿名関数の中の console.log(i); の i とか plus[i] の i は 3 に設定されるのでしょう。
退会済みユーザー

退会済みユーザー

2019/08/14 06:46 編集

var i = 0 を let i = 0 に変えると、Chrome とか Firefox であれば無名関数が 3 つ、それぞれ内部の console.log(i); の i とか plus[i] の i が 0, 1, 2 のものが plus[0], plus[1], plus[3] の click イベントのリスナとしてアタッチされるからうまくいくのだと思います。 でも、IE11 の場合は var i = 0 の場合と同じになって期待通りいかないということになっているようです。
guest

回答6

0

varだと1つの変数をずっと使います。なので、forを抜けた時点でi == plusa.lengthになっていますが、これをclickで起動する無名関数で参照して、その添え字で配列要素を参照しようとして範囲外エラーになります。

letだと変数がブロック毎なので、初回にブロックを実行したときに定義される無名関数中ではi==0です。
varしか無いときは、ブロック内にもう一つ無名関数を作ってその関数内のvarで行う必要がありました。

投稿2019/08/13 03:15

otn

総合スコア84538

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/08/14 02:15

> letだと変数がブロック毎なので、初回にブロックを実行したときに定義される無名関数中ではi==0です。 IE11 の場合は let に変更しても無名関数の中の i は plusa.length となって期待通りにはならないようです。
YESYUKI17

2019/08/14 05:59

※わかりやすように少しコードを書き換えました 理解に時間がかかり返信が遅くなりました、ありがとうございます。 varだと1つの変数をずっと使います。なので、forを抜けた時点でi == plusa.lengthになっていますが まさしくここが私がつまづいている部分だと思います。 私の理解ですと for (var i = 0; i < 5; i++) //まずforの中が実行される① //②が実行されたらまたforの中に戻ってカウンターをあげて次に処理に③ { 処理;//処理が実行される② } という感じでforの()の中のiが定めた上限に到達する前①②③を繰り返すという理解です。 ここでおっしゃっているforを抜けた時点でというのは、for文を規定の回数回した後という理解でよろしいでしょうか? その場合、なぜどこにも処理を書いていないi == plusa.lengthという代入が行われるのでしょうか? またvarだと1つの変数をずっと使いますとありますが for (var i = 0; i < 5; i++) { console.log(i); } という基本的なfor文を書いた場合、ちゃんと0−4までの変数がiに代入されると思うのですが、またvarだと1つの変数をずっと使うとはどう理解したらよいでしょうか?
YESYUKI17

2019/08/14 06:01

アドバイスいただけると大変助かります。
otn

2019/08/14 07:14

> なぜどこにも処理を書いていないi == plusa.lengthという代入が行われるのでしょうか? i++ していって、i < plusa.length が偽になったとき、つまり、i == plusa.length が真になった時にループを抜けますよね? > という基本的なfor文を書いた場合、ちゃんと0−4までの変数がiに代入されると思うのですが、 代入の意味、もしくは変数の意味がおかしいです。 1つの i という変数の値が、0~4まで変化します。 (そして i が5になった時に、条件が偽になって、ループを終了します)
YESYUKI17

2019/08/14 07:37

そして i が5になった時に、条件が偽になって、ループを終了します →以降ループは回らないがvarの場合はこのi = 5はfor文を抜けだして参照?されるという事ですね。 ループが終了した条件は自動的に破棄されると勘違いしておりました。ありがとうございます。 ここで僕のコードなのですが、 for (var i = 0; i < plus.length; i++) { console.log(i) // 期待した通り012と表示 plus[i].addEventListener('click', () => { console.log(i); // 3になる原理は理解できました。 ここでイベントリスナーのブロック内のiが表示する3はループを抜け出した後の変数iになるかと思います。 という事はループを回している間?のiはイベントリスナーのブロックの中から参照できないという事になるのでしょうか?おそらくこれがブロックスコープとの違いという事になるのでしょうが、varだと一つの変数をずっと使いますと言われているところがまだいまいち理解できていないようです。イベントリスナーの前に書かれたコンソールのiは0−3の数字をちゃんと返してくれているので、1つの変数をずっと使うのイメージが持てません。理解力が乏しく大変恐縮ですが、アドバイスいただけると大変助かります。
otn

2019/08/14 07:53

> ループを回している間?のiはイベントリスナーのブロックの中から参照できないという事になるのでしょうか? ちょうどループを回っている何ミリ秒(マイクロ?)かの瞬間にクリックを押せばループを回っている最中のiの値を参照可能です。 > イベントリスナーの前に書かれたコンソールのiは0−3の数字をちゃんと返してくれているので、1つの変数をずっと使うのイメージが持てません。 「変数とは何か」の理解ができてないと思われます。 一体どんな誤解をしているのかわからないので、アドバイスしにくいです。 変数と値の区別がついていないのでしょうかね。 i = 1; console.log(i); i = 2; console.log(i); は、1つの変数に2つの値を順番に代入していると言う事はわかりますか?変数は2つあると思いますか?
think49

2019/08/14 10:50

> ちょうどループを回っている何ミリ秒(マイクロ?)かの瞬間にクリックを押せばループを回っている最中のiの値を参照可能です。 for 文は同期コードで、JavaScriptはシングルスレッド動作なので、割り込み処理は不可能と思います。 イベントリスナが実行される時、forループが完了している事は保証されています。
otn

2019/08/14 11:45

↑ああ、なるほど。
YESYUKI17

2019/08/15 04:19

確かに、変数と値の概念をそれぞれ曖昧に捉えていたと思います。
YESYUKI17

2019/08/15 04:38

このご説明が端的にでとてもわかりやすいという事がやっと理解できました。
guest

0

ベストアンサー

ブロックスコープ

let, const 共に存在する性質ですが、for 文などの繰り返し構文で変数宣言された場合、ブロック毎に「新しい変数」が生成されます。

下記に疑似コードを書きました。

HTML

1<h2>let + var (type1) ※IE11は未対応</h2> 2<p class="plus1">+</p> 3<p class="plus1">+</p> 4<p class="plus1">+</p> 5 6<h2>let + var (type2) ※ブロックスコープの再現コード</h2> 7<p class="plus2">+</p> 8<p class="plus2">+</p> 9<p class="plus2">+</p> 10 11<script> 12'use strict'; 13/* 14 * type 1 15 */ 16const plus1 = document.querySelectorAll('.plus1'); 17 18for (let i = 0; i < plus1.length; i++) { 19 plus1[i].addEventListener('click', function handleClick () { 20 console.log(i); 21 plus1[i].textContent = plus1[i].textContent === '+' ? '-' : '+'; 22 }, false); 23} 24</script> 25<script> 26'use strict'; 27/* 28 * type 2 29 */ 30 const plus2 = document.querySelectorAll('.plus2'); 31 32for (let i = 0; i < plus2.length; i++) { 33 let i2 = i; 34 35 plus2[i2].addEventListener('click', function handleClick () { 36 console.log(i2); 37 plus2[i2].textContent = plus2[i2].textContent === '+' ? '-' : '+'; 38 }, false); 39} 40</script>

変数 i2 はブロック文の中でのみ有効であり、0~2が格納された3つの変数は別の変数です。
変数 i も同様の扱いです。

問題点

  • HTML上で同じID(id="plus")を複数回記述する事は出来ません(文法違反)
  • CSSのIDセレクタは初めに出現した要素のみに適用される仕様です

変数の巻き上げ(Variable hoisting)

JavaScript では変数の「初期化」と「代入」は分けて実行されます。

  • 変数は関数呼び出し時に生成されます
  • 関数呼び出し直後に、関数内で宣言された**全ての変数を undefined で初期化+*します
  • var a = 1; が実行された時、1 が代入されます(初期化は既に完了しています)

JavaSctipt

1function sample1 () { // 変数 a が undefined で「初期化」される 2 console.log(a); // undefined 3 4 var a = 1; // 1 が「代入」される 5} 6 7sample1();

この挙動は分かりづらい為、あえて初期化と代入を分けてコートを書く事を好む人もいます。

JavaSctipt

1function sample2 () { // 変数 a が undefined で「初期化」される 2 var a; // 初期化済なので何もしないが、このコードはundefined で初期化を意図している為、分かりやすい 3 4 console.log(a); // undefined 5 a = 1; // 1 が「代入」される 6} 7 8sample2();

for 文の挙動は下記になります。

JavaSctipt

1function sample3 () { // 変数 elememts,i,len が undefined で「初期化」される 2 3 var elememts = document.querySelectorAll('.plus'); // NodeListが「代入」される(3つの要素を持つとする) 4 5 for (var i = 0, len = elememts.length; i < len; i++) { // i, len が代入される(3回繰り返し) 6 elements.addEventListener('click', () => console.log(i), false); // clickリスナー実行時にはループ処理が終わっている為、i 値は常に3となる 7 } 8} 9 10sample3();

ところで、let, const でも巻き上げは発生しています。
ただし、変数定義文より前に変数参照が行われると、ReferenceError で停止する為、事実上、巻き上げを考慮する必要がなくなっています。

Re: YESYUKI17 さん

投稿2019/08/13 10:51

編集2019/08/14 09:39
think49

総合スコア18162

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2019/08/13 11:03 編集

イメージ的には「ループ回数分だけブロック文が作られる」が分かりやすいかと思います。 for 文を分解して、「ブロック文を複数回書くコード」に変換している、と考えてみて下さい。
YESYUKI17

2019/08/14 05:58

※わかりやすように少しコードを書き換えました 色々基本的なところが理解できていないようで、スコープなど一から調べていたらお返事遅くなりました。 ※idの重複はケアレスミスでした。ご指摘ありがとうございます。 ご説明のおかげでvarはグローバルスコープ、letはブロックスコープでブロックスコープなら繰り返しごとに変数がそれぞれ代入されるということまでは理解できました。 ただ数点まだ理解できていないことがあります。 的外れな点もあるかと思いますが、アドバイスいただけると幸いです。 ブロックごとの処理を期待するのであればletが望ましいこと理解できました。 おそらくこれがみなさまがご説明いただいている点になるかと思いますが、それではvarの場合は内部的にどの処理がされているのでしょうか?varの場合もforのカウンターは回っていて、変数自体は代入されていると思うのですが、ここでは0、1、2と3回、回された処理?はどこに行ってしまったのでしょうか?また変更後のコードの通り、イベントリスナーのブロック内にiを記述すると、3という数字が割り当てられます。そもそもここでは i < 3となるはずなので、そもそも3という数字が生成されるのはおかしいような気がするのです。
think49

2019/08/14 10:51

親記事に追記しました。
think49

2019/08/15 09:05 編集

> そもそもここでは i < 3となるはずなので、そもそも3という数字が生成されるのはおかしいような気がするのです。 ループから抜けるには繰り返し条件式(i < plus.length)は false でなければならないので、i === 3 になります。
YESYUKI17

2019/08/15 04:17

ありがとうございます。やっと理解できたと思います。これはループ処理において、varのスコープがブロックスコープ にならないため、3回のループ処理が全て一つのブロックにまとめられてしまい、関数内の変数は呼び出し時に代入されるため、クリックイベント時に変数iを代入する際に、ループを抜けてi = 3になったiを参照してきてしまうという事が問題なのですね。対して、letであれば各ループがそれぞれ一つのブロックとして完結するので、それぞれその際に生成されたiの値を参照できる。なので関数呼び出し時に値が代入されてもそのループで生成されたiをちゃんと参照できるというなんですね。関数呼び出し時に変数に値が代入されるという観点の漏れが私の理解を妨げていたような気がします。varだと ① plus[i].addEventListener('click', () => { ② if (plus[i].textContent === '+') { クリックを押して関数が呼び出されると、①のiに入っている値と②のiに入っている値が違う上に②の値はループを抜けた後のiを参照してしまうという事で理解しました。この理解で問題ないでしょうか?
think49

2019/08/15 09:27

> 3回のループ処理が全て一つのブロックにまとめられてしまい、 varは関数スコープなので、「ブロック」は妥当でなく、「同じ変数 i を参照してしまい」になるかと思います。 > ① plus[i].addEventListener('click', () => { > ② if (plus[i].textContent === '+') { var においては、同じ変数 i を参照しますが、「参照するタイミング」が異なり、②は「forループを抜けた後の変数 i」を参照します。 他は問題ないと思います。
YESYUKI17

2019/08/15 11:38

ありがとうございます。大変勉強になりました。
guest

0

FAQですね。
for文におけるletとvarの挙動の違いについてQiitaに記事を書いたことがあります。

実はletのメモリ効率が悪かった

varとletは箱の数が違います。
varは1つ、letは箱の数がいっぱいあります。

letとvarを箱の数の違いで表現されていますが、もう少し詳しく、箱のというイメージで捉えるためのご説明をお願いできませんか?

箱のイメージ

比喩でなく説明すると、箱はメモリ上の場所です。

letはブロックスコープなので、forの繰り返しに対して別の箱が用意されるようです。
varはforの繰り返しに対して同じ1つの箱を使い回しします。

varのときクリックイベントで1つの箱を使い回そうとしたときループが進んで別の値に変わってしまっていたというのがよくある不具合です。

またリンク先の説明でvarとletは箱の数が違うに関して
のところでconsoleに4が表示されるのはなぜでしょうか?

for文の説明

他のteratailの質問でfor文の制御フローを解説するために作成した図です。
変数i=3のとき終了条件i<4が真になり、命令文本体が実行されます。
次に「増減」のi++が実行され、i=4となり終了条件i<4が偽になり、for文が終了します。
forループ後にi=4となります。

投稿2019/08/13 10:51

編集2019/08/14 08:05
querykuma

総合スコア777

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YESYUKI17

2019/08/14 05:59

※わかりやすように少しコードを書き換えました ご指摘いただいたリンクなど色々調べていて返信遅くなりました。 ご説明ありがとうございます。 すいません、もう少しで理解できそうなのですが、letとvarを箱の数の違いで表現されていますが、もう少し詳しく、箱のというイメージで捉えるためのご説明をお願いできませんか? またリンク先の説明でvarとletは箱の数が違うに関して のところでconsoleに4が表示されるのはなぜでしょうか? i < 4なので最大でもiは3までの数値になるような気がするのですが。 処理は3回、回っているのにiはなぜ4と表示されているのでしょうか?
querykuma

2019/08/14 08:07

「回答に含めるべき内容は回答に追記しましょう」とあり、コメントではマークダウン記法が使えないようです。 回答を編集しました。
YESYUKI17

2019/08/15 04:18

ありがとうございます。スコープを箱でイメージする事がやっとつかめました。 最終的にブロックないで完結できるか、できないかでどの時点での変数iを参照するかが変わってきて、 それが結果に大きく関わってきたという事ですね。
YESYUKI17

2019/08/15 04:35

for (let i = 0; i < plus1.length; i++) この部分がブロックだとループの数だけそれぞれ独立した箱として用意されるイメージだけど varだと一つのfor (let i = 0; i < plus1.length; i++)を全てのループに使い回す。 なので、クリックで関数を呼び出した時にループを抜けたi = 3を参照してきてしまうというような イメージであっていますでしょうか?
querykuma

2019/08/15 08:39

varだと一つのvarの箱をfor (var i = 0; i < 3; i++)を全てのループに使い回す。 なので、クリックで関数を呼び出した時にループを抜けたi = 3を参照してきてしまうということです。
YESYUKI17

2019/08/15 11:38

ありがとうございます。おかげで理解する事ができました。
guest

0

認識しないvarでエラーになる理由がわかりません。

addEventListenerで割り当てた関数内でiの値を確認してみてください。

【varよりすごいletとconst。(現代的JavaScriptおれおれアドベントカレンダー2017 – 02日目) | Ginpen.com】
https://ginpen.com/2017/12/02/var-let-const/

投稿2019/08/13 02:55

kei344

総合スコア69407

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YESYUKI17

2019/08/14 06:01

※わかりやすように少しコードを書き換えました ありがとうございます。返信に時間がかかってすいません。自分なりに理解し、質問に落とし込むのに時間がかかりました。もう少しで理解できそうなので、アドバイスもらえると大変助かります。 リンク先の説明で なぜかというと、 setTimeout() はすぐに実行されて、コールバック実行の待機を開始し、ループは終了します。その後 setTimeout() のコールバックが実行される頃には i の中身がもう 5 になっちゃってるからです、 とあります。確かにご指摘の通り、イベントリスナーの中にconsole.logでiを表示させると 3と表示されます。setTimeoutの説明でI < max = 5となっているのに5と表示されるのはなぜなんでしょうか?
kei344

2019/08/14 06:25

for (var i = 0; i < 5; i++) forは上記条件の時、初回ループ前にi = 0を実行し、最初のループを実行します。 ループが1回終わった時点で i++をします。 そのあと、 i < 5であれば次のループを実行し、そうでなければ終了します。 スコープがブロックスコープで無いvar の場合、for の後のi は 5になります。
YESYUKI17

2019/08/14 07:14

i < 5を超えたら(i = 5になった時点で)それ以降は{}のループは回さないが、iに5を代入する処理は行われるという理解であってますか? var max = 5; for (var i = 0; i < max; i++) { setTimeout(() => { console.log(max - i); }, i * 1000); } 確かに上のコードを実行すると、0が5回カウントされることが分かります。 これはつまり、for文の()内のiは0-4の値が5回代入されそれが5回分のループ処理と認識されるが、setTimeout関数のブロックの中にはループを回している途中のiは入っていくことができず、forを抜けた時のi=5のみが入っていく事ができるのでconsole.log(max - i);が(max = 5) - (i = 5) = 0が表示されるという理解でよろしいでしょうか?
kei344

2019/08/14 10:52

「i < 5を超えたら」を判断する前に「iに5を代入する処理」は行われています。 後半の処理は「入っていく」など例がよくわかりません。0が表示されるという事象はそのとおりです。
guest

0

余計に混乱させてしまうかもしれませんが、varでなんとかしようとしたら以下のようにしないとだめですね。わからなければ、即時関数、クロージャーも合わせて調べると良いです。

js

1'use strict'; 2{ 3 4 const plus = document.querySelectorAll('.plus'); 5 for (var i = 0; i < plus.length; i++) { 6 console.log(i); 7 (i => { 8 plus[i].addEventListener('click', () => { 9 console.log(i); 10 if (plus[i].textContent === '+') { 11 plus[i].textContent = '-'; 12 } else { 13 plus[i].textContent = '+'; 14 } 15 }); 16 })(i); 17 } 18}

投稿2019/08/15 00:00

nullbot

総合スコア910

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YESYUKI17

2019/08/15 11:46

ありがとうございます。おかげで即時関数と特にクロージャーの理解の必要に気づく事ができました
guest

0

【追記2】注:質問のタイトルにある「for文における、letとvarの挙動の違い」に限って話をしろと言うことですと、私の回答はズレているかもしれませんが、そういう話に限定するわけではなくて、質問にあるサンプルコードが動くようにするにはどうすべきかという話だと理解して回答しています。ちゃんと動くコードを書くというのが本来の目的のはずで、var と let の違いを知りたいというのは目的を果たすための手段の一つでしかないと自分は思うので。

var でも let でもどちらも問題で、リスナに渡される event から target でイベントを発生させた要素を取得し、それを操作しないと期待通りにならないと思いますが?

分かりやすく書くと以下のような感じです。(スクリプトは質問者さんのコードの js/main.js にあるのが前提)

const plusa = document.querySelectorAll('.plus3'); for (var i = 0; i < plusa.length; i++) { plusa[i].addEventListener('click', listener); } function listener(event) { if (event.target.textContent === '+') { event.target.textContent = '-'; } else { event.target.textContent = '+'; } }

【追記】

IE11 での結果です。IE11 では () => は使えないそうなので function () に変えています。Chrome, Edge, Firefox は期待通り動きます。

イメージ説明

投稿2019/08/13 03:52

編集2019/08/14 01:51
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2019/08/13 06:42

> var でも let でもどちらも問題で、...期待通りにならないと思いますが? 未確認ですが、letは期待通りの動作になっていませんか。 (メモリ効率的な側面では、この回答が適していると思います。 同じ状況なら、私も同様のコードを書きます。)
退会済みユーザー

退会済みユーザー

2019/08/13 07:03

> 未確認ですが、letは期待通りの動作になっていませんか。 IE11 はダメでした。Edge とか Chrome は let に変えれば動きましたが。上の私の回答のコードは IE11 でも、もちろん Chrome, Edge でも動くことは確認しています。 個人的には、例え IE11 は無視するとしても、メモリ効率の違いがあるにしても(どう違うのか分かりませんでしたが)、var だとか let だとかの違いを意識しなければならない分かり難いコードは書かない方がよさそうだと思うのですけど。
退会済みユーザー

退会済みユーザー

2019/08/13 07:53

【追伸】 IE11 での結果の画像を回答欄にアップしておきます。
think49

2019/08/13 10:55

To: SurferOnWww さん 回答しました。 https://teratail.com/questions/205789?reply=true#reply-304155 > var だとか let だとかの違いを意識しなければならない分かり難いコードは書かない方がよさそう スコープが違う変数の違いを意識するのは自然な事ではないでしょうか。
退会済みユーザー

退会済みユーザー

2019/08/13 12:57 編集

> スコープが違う変数の違いを意識するのは自然な事ではないでしょうか。 どうでしょう? JavaScript はほとんど知らない自分のような者にとっては、var とそれを付与しない場合の違いだけ理解できていていれば十分と思います。使いこなしたい人にとっては、var と let の違いを意識するのは大事かもしれませんが・・・ 何にせよ、自分にとっては質問のような var と let の使い分けのコードを書くのは分かり難いバグの基になるだけのような気がしてます。その観点で私の書いたコードに何か問題がありますか? var を let に変えても IE11 ではダメと言うことは検証結果からも確かですし、そのあたりはどう考えてますか?
退会済みユーザー

退会済みユーザー

2019/08/13 13:10

質問のタイトルにある「for文における、letとvarの挙動の違い」に限って話をしろと言うことですと、私の回答はズレているかもしれませんが、そういう話に限定するわけではなくて、質問にあるサンプルコードが動くようにするにはどうすべきかという話だと理解して回答しています。
think49

2019/08/13 14:32

To: SurferOnWww さん 私が選ぶ最適解は https://jsfiddle.net/oztpab8c/ です。 「コードを動く為の改善案を教えて下さい」ならこの回答は正ですが、「原理を教えて下さい」ならNGです。 この質問は、「原理を理解して使いこなす」のが主目的と理解しています。 > JavaScript はほとんど知らない自分のような者にとっては 自分が「知らない原理」が存在していて、それが予期せぬ不具合(バグ)を仕込むリスクがあるという意味では、好ましくない状況と思います。 > var を let に変えても IE11 ではダメと言うことは検証結果からも確かですし、そのあたりはどう考えてますか? 回答にも書いたのですが、IE11 で実装されていない為です。 「ECMAScript 6 compatibility table」を参照ください。 http://kangax.github.io/compat-table/es6/#test-let_for/for-in_loop_iteration_scope
退会済みユーザー

退会済みユーザー

2019/08/14 01:47

> 私が選ぶ最適解は https://jsfiddle.net/oztpab8c/ です。 バブリング利用ですか。 確かに、for ループを回したときの var と let の i の違いに悩むより、その方が良さそうです。IE11 と Chrome, Firefox, Edge の let 対応の違いに悩む必要もないし。 > 「コードを動く為の改善案を教えて下さい」ならこの回答は正ですが、「原理を教えて下さい」ならNGです。 それはどうでしょう? 「原理を教えて下さい」の答えの中に IE11 では制約があることも書いて、動くコードを提案するのか質問者さんにとっても回答者・一般閲覧者にとってベストだったのではないかと思います。動くコードしか書かなかった自分が言うのもアレですけど・・・ 私のスタンスは上の 2019/08/13 22:10 の私のコメントに書いたとおりなので、また余計なやり取りをしないで済むよう、そのあたりを上の私の回答を編集して明記しておきます。
YESYUKI17

2019/08/14 06:04

返信遅くなってすいません。 元々は正しく動くコードがわかればいいかなと思っていたのですが、みなさまの回答もらううちに、原理をしっかり理解した方が後々の為にもなると思いまして。なので今回は原理をしっかり理解したいと思うに至ったとご理解いただければ幸いです。
退会済みユーザー

退会済みユーザー

2019/08/14 07:44 編集

質問者さんが質問に追記した以下に対するレスを間違えて質問のコメントに書いてしまいました。ここにもコピーしておきます。 > なぜ3が表示されるのか?i<plus.lengthは3未満の数値になるはず for (var i = 0; i < plus.length; i++) で plus.length は 3 なので、i は 3 までインクレメントされてからループを抜け、匿名関数の中の console.log(i); の i とか plus[i] の i は 3 に設定されるのでしょう。 var i = 0 を let i = 0 に変えると、Chrome とか Firefox であれば無名関数が 3 つ、それぞれ内部の console.log(i); の i とか plus[i] の i が 0, 1, 2 のものが plus[0], plus[1], plus[3] の click イベントのリスナとしてアタッチされるからうまくいくのだと思います。 でも、IE11 の場合は var i = 0 の場合と同じになって期待通りいかないということになっているようです。
退会済みユーザー

退会済みユーザー

2019/08/14 07:04 編集

> 原理をしっかり理解した方が後々の為にもなると思いまして。 そこを理解しても IE11 では話が違ってくるので、そこまで理解しないと問題は回避できないようです。どういう話かと言うと、自分がググって調べた限りでは以下の記事の 2 つ目の回答が分かりやすかったので紹介しておきます。興味があれば見てください。 What browsers currently support JavaScript's 'let' keyword? https://stackoverflow.com/questions/2356830/what-browsers-currently-support-javascripts-let-keyword この問題で悩んだ人は多いようで ie11 let などをキーワードにググると他にもいろいろヒットします。 上のコメントにも書きましたが、例え IE11 は無視するとしても、var だとか let だとかの違いを意識しなければならない分かり難いコードはバグの基になるので書かない方がよさそうだと思います。 自分が JavaScript の書き方を知らないせいだと言われるかもしれませんが、今回の質問者さんのコードのように、リスナの中の i をループを回して設定するなどというのは気持ち悪すぎです。そういうことをしないで、私が上の回答で提案したコードや think49 さんが上のコメントの https://jsfiddle.net/oztpab8c/ で提案されているコードのようにするのがよさそうです。
think49

2019/08/14 11:02

IE11で動作しないのは大きな問題ではありません。 IE11未対応で良いサイトもありますし、トランスコンパイラで変換すれば解決可能です。
退会済みユーザー

退会済みユーザー

2019/08/14 11:46 編集

それはあなたにとっては大きな問題ではないというだけです。あなたの横レスは不要です。お控えください。
think49

2019/08/14 11:51

回避手段を書かないのは公平性に欠けるので横レスしました。それだけです。
退会済みユーザー

退会済みユーザー

2019/08/14 11:57

何を言ってるのか分かりませんが、とにかくあなたの横レスは不要と申し上げた通りです。よろしくお願いします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問