2016-07-24 49 views
1

我是來自Python和Clojure的javascript/dom noob,引用dom中的東西的語義讓我很困惑。當通過dom元素循環時,何時使用「this」以及何時使用循環變量?

採取從一些代碼,我只是寫成瀏覽器擴展程序,以識別匹配testdownloadpage定義的標準頁面的子集,然後掛在PDF下載鏈接事件監聽器攔截並颳去他們:

function hanglisteners() { 
    if (testdownloadpage(url)) { 
     var links = document.getElementsByTagName("a"); 
     for (i = 0, len = links.length; i < len; i++) { 
      var l = links[i]; 
      if (testpdf(l.href)) { 
       l.addEventListener("click", function(e) { 
        e.preventDefault(); 
        e.stopPropagation(); 
        e.stopImmediatePropagation(); 
        getlink(this.href); 
       }, false); 
      }; 
     }; 
    }; 
}; 
以下摘錄

最初,我打電話getlink底部閱讀getlink(l.href),我想(顯然天真)該代碼會發生什麼,它會循環通過每個匹配的鏈接,並打擊每個調用getlink的監聽器在該鏈接的網址上。但那根本不起作用。我試圖用this.href代替l.href,並開始工作。

我不確定爲什麼this.href工作時l.href沒有。我最好的猜測是,JavaScript解釋器不會在addEventListener調用中評估l.href,直到某個點後,l已更改爲其他(??)。但我不知道爲什麼應該是這樣,或者如何知道javascript何時評估函數調用的參數,以及何時不會...

現在我擔心上層調用testpdf(l.href)。該功能旨在檢查以確保鏈接是一個PDF下載鏈接,然後再將監聽器懸掛在該鏈接上。但是,是否要在循環內評估l.href,並因此正確評估每個鏈接?或者是在循環之後的某個時刻也要評估,我應該使用this.href來替代嗎?

請問一些靈魂可以向我解釋底層的語義,這樣我就不必猜測引用循環變量還是提到this是正確的?謝謝!

編輯/添加

的共識似乎是,我的問題是(顯然知名的)問題,即在環內功能的範圍內泄漏S.T.受害者當它們被調用時,除非內部函數關閉它所使用的變量,否則它們最終綁定到循環的最後一個元素。但是:爲什麼這個代碼然後工作?

var links = document.getElementsByTagName("a"); 
 
for (i = 0, len = links.length; i < len; i++) { 
 
    let a = links[i]; 
 
    a.addEventListener("click", function(e) { 
 
    e.preventDefault(); 
 
    e.stopPropagation(); 
 
    e.stopImmediatePropagation(); 
 
    console.log(a.href); 
 
    }); 
 
};
<html> 
 
<head> 
 
    <title>silly test</title> 
 
</head> 
 
<body> 
 
    <p> 
 
    <a href="link1">Link 1</a> 
 
    <a href="link2">link2</a> 
 
    <a href="link3">link 3</a> 
 
    </p> 
 

 

 
</body> 
 
</html>

基於這些問題的答案,我期望點擊每一個環節登錄「鏈接3」,但它們實際上記錄正確的/預期天真結果...

+0

看起來像一個重複的http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – elclanrs

+0

當一個函數被用作事件處理程序,它的'this'被設置到事件觸發的元素(某些瀏覽器不遵循此慣例,用於使用除addEventListener之外的其他方法動態添加的偵聽器)。 –

+0

嗯@elclanrs你是說這個問題與循環沒有任何關係,但是有一些與範圍泄漏到內部函數? –

回答

0

當您處於事件偵聽器的功能中時,this被綁定到事件偵聽器所針對的DOM元素。想象一下下面的代碼:

var links = document.getElementsByTagName("a"); 
for (i = 0, len = links.length; i < len; i++) { 
    let a = links[i]; 
    a.addEventListener("click", function(e) { 
    console.log(this === a); // => true 
    }) 
} 

兩個是相同的,所以您可以使用每當

+0

嗯...我只是測試這個來驗證它自己的工作,但現在額外的困惑,爲什麼我的代碼在這個問題可以與'this'一起使用,但是會因爲循環變量而失敗......(我想我可能還有一些其他的bug!可能必須開始挖掘通過提交歷史記錄,以確保...) –

+0

@PaulGowder,請參閱上面的註釋中鏈接的問題,問題是:loop + closure。 – elclanrs

+0

謝謝@elclanrs –

0

問題的出現是因爲循環變量已經被聽者實際觸發時間改變。你應該能夠看到下面的例子。

function setup(){ 
 
    var root = document.getElementById('root'); 
 
    var info = document.createElement('div'); 
 
    
 
    for (var i = 0; i < 5; i++){ 
 
    // create a button 
 
    var btn = document.createElement('button'); 
 
    
 
    // set up the parameters 
 
    btn.innerHTML = i 
 
    btn.addEventListener('click', function(event){ 
 
     info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML); 
 
    }) 
 
    
 
    // add the button to the dom 
 
    root.appendChild(btn) 
 
    } 
 
    var info = document.createElement('div'); 
 
    info.innerHTML = 'The Value of i is ' + i 
 
    root.appendChild(info) 
 
} 
 

 

 
// run our setup function 
 
setup();
<div id="root"> 
 
    
 
</div>

,所以你需要做的是節省掉的l副本供以後使用。正是由於這個原因,addEventListener自動將元素存儲在this中。另一個很好的方法來做到這一點,但是如果你不想因爲某種原因而混淆this就是使用生成器函數(一個創建函數的函數)。

例如:

function clickListener(i, info){ 
    return function(event){ 
     info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML); 
    } 
} 

這樣我們就可以把變量iinfo到範圍爲它後來被稱爲無值改變監聽功能。

把這個代碼放到上面的例子,我們可以得到下面的代碼片段

function clickListener(i, info){ 
 
    return function(event){ 
 
     info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML); 
 
    } 
 
} 
 

 
function setup(){ 
 
    var root = document.getElementById('root'); 
 
    var info = document.createElement('div'); 
 
    for (var i = 0; i < 5; i++){ 
 
    // create a button 
 
    var btn = document.createElement('button'); 
 
    
 
    // set up the parameters 
 
    btn.innerHTML = i 
 
    // use the generator to get a click handler 
 
    btn.addEventListener('click', clickListener(i, info)) 
 
    
 
    // add the button to the dom 
 
    root.appendChild(btn) 
 
    } 
 
    info.innerHTML = 'The Value of i is ' + i 
 
    root.appendChild(info) 
 
} 
 

 

 
// run our setup function 
 
setup();
<div id="root"> 
 
    
 
</div>

編輯:回答修訂問題

在你的代碼的第二次迭代中,您使用與ES6一起引入的let關鍵字。此關鍵字專門爲JavaScript變量提供更多傳統的塊範圍。在我們的例子中,它將變量作用於循環的特定迭代。在MDN上可以看到類似的例子。如果您的應用程序/構建系統中支持ES6,使用let是解決問題的好方法。