ホームページ >ウェブフロントエンド >jsチュートリアル >JS FAQ: クリックするとポップアップ表示される i が常に最後のものになるのはなぜですか_JavaScript ヒント
フロントエンドグループでこの質問をしている人がたくさんいたのですが、今夜も同じ質問をする人がいたので、それを整理するために記事を書きました。まず、コードを見てください。li をクリックすると、現在の li に対応するインデックス値がポップアップ表示されます。非常に多くの人が次のコードを書きました。
var aLi = document.getElementsByTagName('li'); for(var i = 0; i < aLi.length; i++){ aLi[i].onclick = function(){ alert(i); } }
しかし、結果は満足のいくものではありません。簡単にするために、ページ内に 2 li があることに同意します。 li をクリックすると、すべてのポップアップが 2 になります。
まず、結果が 1 になる理由を分析しましょう。ループは単純に 2 つの部分に分割できます。
i = 0时,aLi[0].onclick = function(){alert(i)} i = 1时,aLi[1].onclick = function(){alert(i)} i = 2时,不满足条件跳出循环.
クリック関数を実行すると、スコープ チェーンが作成されます。このスコープ チェーンは、コード スコープ内の変数を定義するオブジェクトのリストです。 (変数オブジェクトの内容を詳しく知りたい場合は、変数オブジェクトを表示できます。)alert(i) を実行すると、変数 i を内側から外側に向かって探します。この時点で、この関数のスコープチェーンには 2 つのオブジェクトがあり、ループは終了しており、このときの i の値は 2 です。したがって、任意の li をクリックすると、ポップアップが表示されます。 2、必要なインデックス値ではありません。重要なのは、ポップアップされるのは変数 i、変数 i、変数 i であるということです。大事なことは3回言いましょう。
そこで、この問題をどのように解決するかという問題が生じます。必要なのは、イベントを aLi[i] にバインドするたびに、内部スコープに i の値を保存することです。解決策は次のとおりです。
var aLi = document.getElementsByTagName('li'); for (var i = 0; i < aLi.length; i++) { (function(i){ aLi[i].onclick = function () { alert(i); }; })(i) }
これには、ブロックレベルのスコープの概念が含まれます。 ES6 が登場する前は、関数がブロックレベルのスコープを作成する主な手段でした。ここでは、i をパラメーターとして使用して、aLi[i].onclick の外側に関数のレイヤーを配置し、結果を再分析してみましょう。
i = 0时, (function(i){ aLi[0].onclick = function(){ alert(i); } })(0) i = 1时, (function(i){ aLi[1].onclick = function(){ alert(i); } })(1) i = 2时,不满足条件跳出循环.
自己実行関数のラッピング層が追加されたため、li をクリックすると、内側から外側に向かって 3 つのレベルのスコープが存在します: クリック関数内の変数オブジェクト、クリック関数の変数オブジェクト。自己実行関数とレイヤーの最も外側のスコープ。 2 番目のレベルを検索すると、自己実行関数の i が渡されたパラメータ値と同じであることがわかり、そのときの i の値が対応して保存されるため、対応するインデックス値がポップアップ表示されます。
よくある js の問題を共有しましょう。li をクリックすると、現在の li インデックスと innerHTML 関数がポップアップします。
いずれかをクリックすると、次のアラートが表示されます:
通常の考え方によれば、コードは次のように記述する必要があります:
var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){ for(var i = 0, len = list.length; i < len; i++){ list[i].onclick = function(){ alert(i + "----" + this.innerHTML); } } } foo();
しかし、残念なことに、結果は次のようになります:
インデックスが常に 4 なのはなぜですか? これは、js にブロックレベルのスコープがないことが原因です。解決策は 3 つあります
1. クロージャを使用する
<script type="text/javascript"> var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){ for(var i = 0, len = list.length; i < len; i++){ var that = list[i]; list[i].onclick = (function(k){ var info = that.innerHTML; return function(){ alert(k + "----" + info); }; })(i); } } foo(); </script>
2. ES6 の新機能 let を使用して変数を宣言します
let で宣言された変数はブロックレベルのスコープを持ち、明らかに要件を満たしていますが、有効にするには 'use strict' (厳密モードを使用) を追加する必要があることに注意してください。
<script type="text/javascript"> var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){'use strict' for(let i = 0, len = list.length; i < len; i++){ list[i].onclick = function(){ alert(i + "----" + this.innerHTML); } } } foo(); </script>
3. jquery を導入し、イベント バインディングに on または delegate を使用します (これらはすべてイベント プロキシの特性を備えています)
<script type="text/javascript" src="jquery-1.8.2.min.js"></script> <script type="text/javascript"> $("ul").delegate("li", "click", function(){ var index = $(this).index(); var info = $(this).html(); alert(index + "----" + info); }); </script> <script type="text/javascript"> $("ul").on("click", "li", function(){ var index = $(this).index(); var info = $(this).html(); alert(index + "----" + info); }); </script>