当循环通过dom元素时,何时使用“this”以及何时使用循环变量?

时间:2016-07-24 16:18:50

标签: javascript dom this

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

将我刚写入的一些代码中的以下摘录转换为chrome扩展,以识别符合testdownloadpage中定义的条件的页面子集,然后在pdf下载链接上挂起eventlisteners来拦截和刮擦它们:

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的一个监听器上。但那根本不起作用。我尝试用l.href替换this.href作为猜测,它开始工作了。

this.href没有l.href时,我不确定为什么l.href有效。我最好的猜测是javascript解释器在addEventListener调用中不会评估l,直到稍后某个时候,testpdf(l.href)已经更改为其他内容(??)。但我不确定为什么应该这样,或者如何知道javascript何时评估函数调用的参数以及何时不... ...

现在我担心对l.href的更高级别的号召。该函数用于检查以确保链接是一个pdf下载链接,然后再将其挂起。但这是否会在循环中评估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>
.btn-primary:focus

基于这些答案,我希望点击每个链接来记录“链接3”,但它们实际上记录了正确/预期的天真结果......

2 个答案:

答案 0 :(得分: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
  })
}

两者是相同的,所以你可以随时使用

答案 1 :(得分:0)

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

&#13;
&#13;
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();
&#13;
<div id="root">
  
</div>
&#13;
&#13;
&#13;

所以您需要做的是保存l的副本供以后使用。出于这个原因,addEventListener会自动将元素存储在this中。另一个非常好的方法,但是如果你因为某些原因不想弄乱this,那就是使用生成器函数(一个创建函数的函数)。

例如:

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

通过这种方式,我们可以将变量iinfo放入侦听器函数的作用域,稍后调用该函数而不会更改值。

将此代码放入上面的示例中,我们得到以下代码段

&#13;
&#13;
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();
&#13;
<div id="root">
  
</div>
&#13;
&#13;
&#13;

编辑:回答修订后的问题

在第二次代码迭代中,您使用let引入的ES6关键字。此关键字专门用于为javascript变量提供更传统的块范围。在我们的例子中,它使变量作用于循环的特定迭代。在MDN上可以看到类似的例子。如果您可以在应用程序/构建系统中支持ES6,则使用let是解决问题的好方法。