当循环通过dom元素时;这个“;以及何时使用循环变量

when looping through dom elements, when to use "this" and when to use loop variable?

本文关键字:循环 何时使 变量 这个 dom 元素      更新时间:2023-09-26

我是一个javascript/dom noob,主要来自Python和Clojure,在dom中引用事物的语义真的让我很困惑。

以下是我刚刚写在chrome扩展中的一些代码的摘录,用于识别与testdownloadpage中定义的标准匹配的页面子集,然后将eventlistener挂在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),我(显然很天真)认为,该代码会循环通过每个匹配的链接,并在该链接的url上的每个调用getlink的链接上加上一个监听器。但这根本不起作用。我试着用this.href替换l.href,只是猜测,它开始工作了。

我不知道为什么this.href有效,而l.href无效。我的最佳猜测是,javascript解释器不会在addEventListener调用中评估l.href,直到稍后l更改为其他内容(??)。但我不知道为什么应该这样,也不知道如何知道javascript何时对函数调用的参数求值,何时不求值。。。

现在我在担心testpdf(l.href)的上级电话。该函数的目的是在将侦听器挂在链接上之前检查以确保链接是pdf下载链接。但这是否会在循环中评估l.href,从而正确评估每个链接?或者这也会在循环后的某个点进行评估,我应该使用this.href吗?

能否请一些灵魂向我解释一下底层语义,这样我就不必猜测引用循环变量或引用this是否正确了?谢谢

编辑/添加

人们一致认为,我的问题是一个(众所周知的)问题,循环中的内部函数是范围泄漏的受害者。当调用它们时,除非内部函数关闭它使用的所有变量,否则它们最终会绑定到循环的最后一个元素。但是:为什么这个代码会起作用?

  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",但它们实际上记录了正确/预期的天真结果。。。

问题的出现是因为在侦听器实际启动时,循环变量已经发生了更改。您应该能够通过下面的示例看到这一点。

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的副本以备将来使用。CCD_ 16正是出于这个原因而自动地将该元素存储在CCD_。然而,如果出于某种原因不想使用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是解决问题的好方法。

当您使用事件侦听器的功能时,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
  })
}

这两者是相同的,所以无论何时

都可以使用