循環一個節點清單(NodeList)或者數組,并且綁定事件處理函數引發對閉包的了解
當然可以使用事件委托來實作,而且性能更好,但是這裡純粹為了了解這個問題。
我在網上查閱了很多資料,有了一點了解,記錄下來。如果有不對的地方,各位看客請指正。
代碼如下:
HTML結構
<div id='one'></div>
<div id='two'></div>
<div id='three'></div>
JS處理
var odivs = document.getElementsByTagName('div');
for(var i=0; i<odivs.length; i++){ //周遊節點清單并且綁定事件
odivs[i].onclick = function(){
alert(i);
}
}
當我們點選任何一個DIV時,彈出的都是3;
這樣會非常讓人疑惑
那麼我們換給寫法。
var one = document.getElementById('one');
var two = document.getElementById('two');
var three = document.getElementById('three');
var i=0;
one.onclick = function(){
alert(i);
}
i++;
two.onclick = function(){
alert(i);
}
i++;
three.onclick = function(){
alert(i);
}
i++ // i=3 大于2 退出循環
從這裡知道,3這邊值是i的最終的值。
然後我們再看事件處理函數。
function(){
alert(i);
}
從這裡可以看出,這是一個函數聲明,在面向對象中,這叫構造函數。
這隻是一個函數聲明,并且綁定到點選事件上,并沒有被執行。然後代碼繼續往下,執行i++;
在網上看到這樣一句話:
之是以是這樣的,看過《JavaScript: The Definitive Guide》就會明白:
所有的JavaScript函數都是閉包:它們都是對象,都關聯到作用域鍊。每次調用JavaScript函數的時候,都會為之建立一個新的對象來儲存局部變量,并把這個對象添加至作用域鍊中(簡單了解:作用域鍊就是對象清單)。
重點:"每次調用javascript函數時",局部變量會在函數被執行的時候建立,而不是聲明的時候建立。
上面的例子,我們很直覺的看出。三個函數中的i共享全局變量i;事件處理函數并沒有被執行,代碼接着往下走,
一直到腳本結束。 i的值變為3.
這時候,點選其中任意一個div,然後顯示的是全局的i,i=3,是以得出了3這個值。
再往下看,可能有人會這麼寫,我就這麼寫過。
for(var i=0; i<odivs.length; i++){
odiv[i].onclick = function(){
return function(o){
alert(o);
}(i);
}
}
一看之下,好像沒什麼問題,變量i被一個内部函數儲存了下來。函數也被執行了。
但是,函數真的被執行了嗎?
分拆開來
odiv[i].onclick = function(){
return function(o){
alert(o);
}(i);
}
綁定了一個事件函數
function(){
return function(o){
alert(o);
}(i);
}
可以看出,并沒有被執行。代碼還是繼續往下走。
并且代碼也不會被執行。當時不知道怎麼就寫了這麼個東西。
js 中隻有 function 才會劃分作用域(和 python 有點像),if/else、for 循環都不會劃分作用域,是以原來的方式循環引用的都是同一個變量 i。
以上,我們可以得出一個結論:
要想把i的值儲存下來。必須在執行過程中把i放到一個函數中,并且函數需要立即執行,這樣0,1,2的值才會被保留下來。
那麼修改代碼
for(var i=0; i<odivs.length; i++){
odivs[i].onclick = show(i);
}
function show(i){
alert(i);
}
那麼,這樣寫行嗎。顯然是不行的。
雖然在綁定事件的時候,調用了一個外部函數,并且将i傳遞進去儲存了下來。
但是,結果會是沒循環一次,調用一次show函數。然後彈出一個數字。并沒有在onclick并沒有綁定事件處理函數,因為show函數并沒有傳回值,或者傳回值為undefined ,是以上面的代碼應該是這樣的
for(var i=0; i<odivs.length; i++){
show(i);
odivs[i].onclick = undefined;
}
好吧,以上是我在學習過程中犯的一些錯誤,和自己慢慢深入後的一些了解。下面正式來說說解決這個問題的辦法。
上面說過:
1.要想把i的值儲存下來。必須在執行過程中把i放到一個函數中,并且函數需要立即執行。
2.函數的傳回值應該是一個事件處理函數。否則事件綁定就沒有意義了。
修改後代碼如下。
for(var i=0; i<odivs.length; i++){
odivs[i].onclick = show(i); // 講變量i傳入函數立即執行,保留下來。
}
function show(i){
return function(){ //傳回一個事件處理函數。函數内部調用alert的方法将i顯示出來。
alert(i);
}
}
//結果 0,1,2 正确。
然後我們寫在一起
通過一個佚名函數實作,道理和上面一樣。
for(var i=0; i<odivs.length; i++){
odivs[i].onclick = (function(i){
return function(){
alert(i);
}
})(i);
}
當然還可以這麼寫
先把i保留下來,再綁定事件。順序反一下。
for(var i=0; i<odivs.length; i++){
(function(i){
odivs[i].onclick = function(){
alert(i);
}
})(i);
}
至此,這個問題處理完畢。
文章中很多來自 http://www.zhihu.com/question/20019257 ,感謝!
百度搜了很多這個問題,排前面的都是幾個li,然後出現這個問題,然後就是一個解決辦法,而且大多數網站都是這樣,抄來抄去,連标點都一樣,很讓人困惑,浪費事件。
上面這篇内容對我啟發很大。講講自己的了解。
我來講講自己的了解。
首先說了,JS其實也分傳值和傳位址 也就是 by value and by sharing. 對應java,C等語言的 by value 和 by reference
var bar;
var foo = bar;
bar = {'key' : 'value'};
console.log(foo , bar );
這樣一個案例。
輸出的是 undefined 和 {'key':value};
從這裡可以看出,foo=bar 是把 bar的值傳遞給了 foo ; 因為一開始 bar沒有指派,值是undefined,是以foo也是undefined,bar再指派。
假如foo=bar 是将 bar的引用傳遞給了 foo的話,那麼 修改bar的值, foo輸出的也應該是修改後的值。
再看如下代碼
var d = new Date();
console.log(d.getMinutes());
var a = d;
a.setMinutes(18);
console.log(d.getMinutes());
// 18
測試的時候是 48 18
很明顯, a = d 時,是将 引用傳遞給了 a ,修改a的屬性時,d也跟着變了。
看JS中的作用域(scope) 看原文,已經寫的不錯了。
自 http://www.zhihu.com/question/20019257
個人的一點心得,還在學習中,如有不對的地方,還望指出。