首先我們來看一段代碼
<body>
<ul id="ul">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
<script type="text/javascript">
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName("li");
for(var i = 0; i < aLi.length; i++){
aLi[i].onclick = function (){
alert(i);
};
}
</script>
</body>
運行之后發現無論點哪個標簽,彈出的都是最后一個標簽的index
這是因為 for 循環的里面 var 定義的變量 i 自動提升為全局變量,等同於下面的代碼
<script type="text/javascript">
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName("li");
var i;
for(i = 0; i < aLi.length; i++){
aLi[i].onclick = function (){
alert(i);
};
}
</script>
這時候 alert(i) 里面的i還沒有值,當用戶調用 onclick 的匿名函數時,需要對i求值
解析程序首先會在事件處理程序內部查找,但 i 沒有定義。然后,又到方法外部去查找,此時有定義,但此時的i已經循環完畢,因此,無論點哪個標簽,彈出的都是最后一個標簽的index。
有以下幾種方法解決:
立即調用的函數表達式(IIFE);不懂的可以看看我之前寫的 點擊這里
<body>
<ul id="ul">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
<script type="text/javascript">
var oul = document.getElementById("ul");
var ali = oul.getElementsByTagName("li");
for(var i = 0; i < ali.length; i++){
(function(i){ //這里的i類似形參
ali[i].onclick = function (){
alert(i);
}
})(i); //這里的i類似實參
}
</script>
</body>
將變量 i 保存給在每個段落對象(li)上
<body>
<ul id="ul">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
<script type="text/javascript">
var oul = document.getElementById("ul");
var ali = oul.getElementsByTagName("li");
for(var i = 0; i < ali.length; i++){
ali[i].i = i;
ali[i].onclick = function (){
alert(this.i);
}
}
</script>
</body>
將變量 i 保存在匿名函數自身
<body>
<ul id="ul">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
<script type="text/javascript">
var oul = document.getElementById("ul");
var ali = oul.getElementsByTagName("li");
for(var i = 0; i < ali.length; i++){
(ali[i].onclick = function (){
alert(arguments.callee.i);
}).i = i
}
</script>
</body>
還有一種使用ES6新語法 let 關鍵字 由於是新語法 各瀏覽器支持不同
<body>
<ul id="ul">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
<li>5555</li>
</ul>
<script type="text/javascript">
var oul = document.getElementById("ul");
var ali = oul.getElementsByTagName("li");
for(let i = 0; i < ali.length; i++){
ali[i].onclick = function (){
alert(i);
}
}
</script>
</body>
關於let,我一直似懂非懂,后來在阮一峰老師的《ECMAScript 6 入門》這本書上找到了答案
使用let,聲明的變量僅在塊級作用域內有效,當前的 i 只在本輪循環有效,所以每次都是一個新的變量
可能你會問,如果每一輪循環的變量 i 都是重新聲明的,那它怎么知道上一輪循環的值,從而計算出本輪循環的值
這是因為Javascript引擎內部會記住上一輪循環的值,初始化本輪的變量 i 時,就在上一輪循環的基礎上進行計算。
題外話
for循環還有一個特別之處,就是循環語句部分是一個父作用域,而循環體內部則是一個單獨的子作用域
for (let i = 0; i < 3; i++) {
let i = "abc" ;
console.log(i);
}
// abc
// abc
// abc
上面的代碼輸出了3次 abc,這表明函數內部的變量 i 和外部的變量 i 是分離的