I'm a javascript/dom noob coming from mainly Python and Clojure, and the semantics of referring to things in the dom are really confusing me.
Take the following extract from some code which I just wrote into a chrome extension to identify a subset of pages matching criteria defined in testdownloadpage
and then hang eventlisteners on pdf download links to intercept and scrape them.:
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);
};
};
};
};
Originally, I had the call to getlink
at the bottom reading getlink(l.href)
, and I thought (evidently naively) that what would happen with that code would be that it would loop through every matching link, and slap a listener on each that called getlink on the url for that link. But that didn't work at all. I tried replacing l.href
with this.href
just as a guess, and it started working.
I'm not sure why this.href
worked when l.href
did not. My best guess is that the javascript interpreter doesn't evaluate l.href
in an addEventListener
call until some point later, when l
has changed to something else(??). But I'm not sure why that should be, or how to know when javascript does evaluate arguments to a function call and when it doesn't...
And now I'm worrying about the higher-up call to testpdf(l.href)
. That function is meant to check to make sure a link is a pdf download link before hanging the listener on it. But is that going to evaluate l.href
within the loop, and hence correctly evaluate each link? Or is that also going to evaluate at some point after the loop, and should I be using this.href
instead?
Can some kind soul please explain the underlying semantics to me, so that I don't have to guess whether referring to the loop variable or referring to this
is correct? Thanks!
EDIT/ADDITION:
The consensus seems to be that my problem is a (clearly well-known) issue where inner functions in loops are victims of scope leaks s.t. when they're called, unless the inner function closes over all the variables it uses, they end up bound to the last element of the loop. But: why does this code then work?
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>
Based on those answers, I'd expect clicking on every link to log "link 3," but they actually log the correct/expected naive results...