我是一个主要来自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”,但它们实际上记录了正确/预期的天真结果......
答案 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)
问题出现是因为循环变量在侦听器实际触发时已经改变了。您应该能够通过以下示例看到这一点。
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;
所以您需要做的是保存l
的副本供以后使用。出于这个原因,addEventListener
会自动将元素存储在this
中。另一个非常好的方法,但是如果你因为某些原因不想弄乱this
,那就是使用生成器函数(一个创建函数的函数)。
例如:
function clickListener(i, info){
return function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
}
}
通过这种方式,我们可以将变量i
和info
放入侦听器函数的作用域,稍后调用该函数而不会更改值。
将此代码放入上面的示例中,我们得到以下代码段
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;
编辑:回答修订后的问题
在第二次代码迭代中,您使用let
引入的ES6
关键字。此关键字专门用于为javascript变量提供更传统的块范围。在我们的例子中,它使变量作用于循环的特定迭代。在MDN上可以看到类似的例子。如果您可以在应用程序/构建系统中支持ES6
,则使用let是解决问题的好方法。