Closures Explained

J

Jorge

I've rewritten a short article explaining closures in JavaScript.
It's
 at:

http://www.martinrinehart.com/articles/javascript-closures.html

A big Thank You to PointedEars and Jorge for helping me get closer to
the truth.

"Some authors said (and this was where I got confused) that the
closure has access to the variables of the outer function. This is
sort of true. Really it has access to the variable's names and values
at the moment the closure is created."

This isn't clear enough... a bit messy.

An inner function that survives keeps working **exactly*as*it*did**
before outer() returned. That's all. I'm not sure what do you mean
with "Really it has access to the variable's names and values at the
moment the closure is created" ? There's not any kind of 'memory
effect' nor nothing like that...
 
L

Lasse Reichstein Nielsen

Jorge said:
On Oct 10, 5:38 pm, (e-mail address removed) wrote:
"Some authors said (and this was where I got confused) that the
closure has access to the variables of the outer function. This is
sort of true. Really it has access to the variable's names and values
at the moment the closure is created."

This isn't clear enough... a bit messy.

It's also wrong. The closure has access to the actual variables, not
just their value at the time the closure was created, including any
change to the variable, before or after the closure was created.T

That's why the following doesn't work as most people expect it to:

for (var i = 0; i < 10; i++) {
setTimeout(function(){alert(i);},i*2000);
}

It alerts "10" all ten times.

/L
 
J

Jorge

I've rewritten a short article explaining closures in JavaScript.
It's
 at:

http://www.martinrinehart.com/articles/javascript-closures.html

"Really it has access to the variable's names and values
at the moment the closure is created."

No 'memory effects': in this example there are 10 different inner
functions that survive after the outer (window.onload) function
returns. They are created at different times and at different values
of 'n'. But they all alert the same value: the value that n has in the
(left orphan by window.onload's return) execution context of the outer
function: 10.

See: http://jorgechamorro.com/cljs/020/

<script>
window.onload= function () {
var e, n;
for (n=0; n<10; n++) {
document.body.appendChild(
e= document.createElement('button'
)).innerHTML= n;
e.onclick= function () { alert(n) };
}
};
</script>
 
J

Jorge

(...)
That's why the following doesn't work as most people expect it to:

 for (var i = 0; i < 10; i++) {
   setTimeout(function(){alert(i);},i*2000);
 }

It alerts "10" all ten times.


(..) But they all alert the same value: the value that n has in the
(left orphan by window.onload's return) execution context of the outer
function: 10.

LOL :)
 
J

John G Harris

Where does he say that?

In the syntax diagrams.

You're quoting him out of context; JSON wouldn't be possible if nested
curlies weren't allowed.

Perhaps I should have said I wasn't talking about literals.

Just a guess, but he was probably talking about
the absence of (traditional*) block scope in JavaScript, and was
suggesting that unnecessary {} blocks shouldn't be used, in order to
avoid confusion. In that context, I would agree with Crockford.

He does say that a block is not a scope. What he doesn't say is that a
block is also a statement. Whether that's for doctrinaire reasons or
from ignorance I don't know.

I don't think he says in words that he doesn't want to nest blocks.
Perhaps he couldn't think of any way of discouraging people from doing

... { ... if (true) { ... } ... } ...


John
 
P

Peter Michaux

It gets off to a dubious start:

"I had the best books (Flanagan, Resig, Crockford)"

Flanagan has been proven clueless

I think "clueless" is incorrect and inappropriate. He knows a lot as
displayed many times in his book and I still find his book a useful
starting resource for most browser-related issues. Yes his book has
mistakes but that does not make him clueless.

Peter
 
D

David Mark

I think "clueless" is incorrect and inappropriate. He knows a lot as

His name has come up here a lot over the years. It seems to me that
most of it was bad. I certainly wouldn't recommend buying his books.
He's no Resig when it comes to cluelessness. I'll leave it at that.
 
P

Peter Michaux

His name has come up here a lot over the years.  It seems to me that
most of it was bad.

People seem to like to point out the mistakes because that is actually
useful. It would be boring and pointless to read about all the he
wrote that are correct.

I certainly wouldn't recommend buying his books.

That is a different story. I would recommend his book but would warn
that there are errors both small and large. I think a beginner is best
served by a paper book that covers the language and the DOM.
Flanagan's book fits that bill best of the options available. It isn't
so good to say to a beginner, "hey there are some documents on there
and there on the web. Try the terse specs and the Mozilla site and the
archives of c.l.js are good but there is a lot of pedantic arguing to
wade through. Just jump in the deep end with the sharks." Flangan's
book is a much more gentle introduction.

Peter
 
S

sasuke

[snip]

A closure is a piece of code together with a binding of values to the
free variables of that code.

A free variable is one that occur in the code, but is not declared
there.

So does it mean that even global variables in Javascript are `free
variables' given the fact that they can be used without being declared
or is it that the definition is a loose one?

Also, given the function:

---------------------------------->B--------------------------
function attachEvents() {
var divs = document.getElementsByTagName("DIV");
if(!divs) return;
for(var i = 0, maxI = divs.length; i < maxI; ++i) {
var d = divs;
d.onclick = function() {
// some complicated processing with a lot of variables
alert("Hello from " + d.id);
}
}
}
window.onload = function() {
attachEvents();
// something complicated
attachEvents();
}
---------------------------------->B--------------------------

Will the second invocation of the function `attachEvents' make the
execution context of the first run along with the previously created
function objects eligible for garbage collection or do they need to be
explicitly grounded [set to null]?

/sasuke
 
L

Lasse Reichstein Nielsen

sasuke said:
[snip]

A closure is a piece of code together with a binding of values to the
free variables of that code.

A free variable is one that occur in the code, but is not declared
there.

So does it mean that even global variables in Javascript are `free
variables' given the fact that they can be used without being declared
or is it that the definition is a loose one?

I variable is not inherently free or not. A variable occurence in a
part of a program, e.g., function declaration, is free in that
function if the function doesn't contain a declaration of that variable.

Example:

function foo(x) {
return function(y) {
alert(x+y);
}
}

This entire function has only one free variable: "alert".
The variable occurences "x" and "y" are bound by the argument
declarations of the surrounding functions.

The inner function:
function(y) { alert(x+y); }
has two free variables: "alert" and "x".

Notice that the same occurence of "x" can be both free and bound
depending on the scope one consider.

Also, given the function:

---------------------------------->B--------------------------
function attachEvents() {
var divs = document.getElementsByTagName("DIV");
if(!divs) return;
for(var i = 0, maxI = divs.length; i < maxI; ++i) {
var d = divs;
d.onclick = function() {
// some complicated processing with a lot of variables
alert("Hello from " + d.id);
}
}
}
window.onload = function() {
attachEvents();
// something complicated
attachEvents();
}
---------------------------------->B--------------------------

Will the second invocation of the function `attachEvents' make the
execution context of the first run along with the previously created
function objects eligible for garbage collection or do they need to be
explicitly grounded [set to null]?


That depends entirely on the javascript implementation.

Best case, nothing remains and the garbage collector claims it all.

Worst case, the garbage collector barfs on the DOM->JS function->DOM
dependencies and collects nothing. That's a memory leak. I believe
IE 6 might do just that.

/L
 
D

David Mark

A closure is a piece of code together with a binding of values to the
free variables of that code.
A free variable is one that occur in the code, but is not declared
there.

So does it mean that even global variables in Javascript are `free
variables' given the fact that they can be used without being declared
or is it that the definition is a loose one?

The term "free variable" (as defined here) could be used to describe
global variables that are referenced within functions. Regardless,
global variables are not part of the binding described in the closure
definition.
Also, given the function:

---------------------------------->B--------------------------
function attachEvents() {
  var divs = document.getElementsByTagName("DIV");

Inefficient. Use the collection itself.
  if(!divs) return;

You don't need to do that with gEBTN.
  for(var i = 0, maxI = divs.length; i < maxI; ++i) {

Inefficient. Use a while that counts down to 0.
    var d = divs;
    d.onclick = function() {
      // some complicated processing with a lot of variables
      alert("Hello from " + d.id);
    }
  }}


You are leaking memory. Every one of these creates this chain:

[DOM element X] ---onclick---> [anon function] ---> [variable object]
---> [DOM element A]

You forgot to set divs to null too. Same issue.

Unfortunately, your design doesn't allow you to set d to null.

function attachEvents() {
var el, index = document.getElementsByTagName('div').length;
while (index--) {
el = document.getElementsByTagName('div')[index];
el.onclick = (function(id) {
return function() { alert('Hello from ' + id); };
})(el.id);
}
el = null;
}

Or better yet, attach one listener and use delegation.
 
J

Jorge

function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];
     el.onclick = (function(id) {
        return function() { alert('Hello from ' + id); };
     })(el.id);
  }
  el = null;

}


function attachEvents () {
var divs= document.getElementsByTagName('div'),
f= function (event) { alert(this.id) },
index= divs.length;

while (index--) { divs[index].onclick= f }
}
 
D

David Mark

[second attempt; the first didn't show up for some reason]

Inefficient.  Use the collection itself.

(see below)
Inefficient.  Use a while that counts down to 0.

(That's micro-optimization, unlikely to produce any measurable
performance gains. It also inverts the order of element processing,
which may be significant in some cases.)

But not in this case.
function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];

Are you suggesting that calling getElementsByTagName() in a loop is more
efficient than storing a reference to the collection in a variable, and

Yes. Browsers optimize for that pattern.
using the variable in the loop? I doubt that, and some preliminary

Doubt it all you want.
testing shows that your recommendation is consistently slower than
sasuke's; in some implementations (Opera, Safari) even by a factor of 4.

Nonsense. Opera has a long article on their site about this.
 
D

David Mark

function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];
     el.onclick = (function(id) {
        return function() { alert('Hello from ' + id); };
     })(el.id);
  }
  el = null;

function attachEvents () {
  var divs= document.getElementsByTagName('div'),
  f= function (event) { alert(this.id) },
  index= divs.length;

  while (index--) { divs[index].onclick= f }

}

It is pointless to rewrite this pattern (diminishing returns.) A
single listener should be used.
 
D

David Mark

[second attempt; the first didn't show up for some reason]
On 2008-10-13 20:56, David Mark wrote:
(see below)
(That's micro-optimization, unlikely to produce any measurable
performance gains. It also inverts the order of element processing,
which may be significant in some cases.)

But not in this case.


function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];
Are you suggesting that calling getElementsByTagName() in a loop is more
efficient than storing a reference to the collection in a variable, and

Yes.  Browsers optimize for that pattern.
using the variable in the loop? I doubt that, and some preliminary

Doubt it all you want.
testing shows that your recommendation is consistently slower than
sasuke's; in some implementations (Opera, Safari) even by a factor of 4..

Nonsense.  Opera has a long article on their site about this.

Trying to find that article, but nothing that exactly matches on the
Opera site. Maybe I am thinking of something else. Regardless, the
while loop is definitely faster in all implementations (no need to
test that) and closures/memory leaks are the issue at hand.

As I mentioned, the best solution is to attach one listener and get
the id from the event target. All of these other loopy patterns
(closures or not) are silly.
 
D

David Mark

function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];
     el.onclick = (function(id) {
        return function() { alert('Hello from ' + id); };
     })(el.id);
  }
  el = null;

function attachEvents () {
  var divs= document.getElementsByTagName('div'),
  f= function (event) { alert(this.id) },
  index= divs.length;

  while (index--) { divs[index].onclick= f }

}

Oops, forgot to mention that this will leak memory in IE.

[divs]->[DOM node]->[f]->[variable object]->[divs]

Add divs = null to break the chain at the end (of course.)

See the link posted previously.
 
L

Lasse Reichstein Nielsen

David Mark said:
[second attempt; the first didn't show up for some reason]

On 2008-10-13 20:56, David Mark wrote:
function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];

Are you suggesting that calling getElementsByTagName() in a loop is more
efficient than storing a reference to the collection in a variable, and

Yes. Browsers optimize for that pattern.

Do you have any references for that? It sounds likely that they do
something smart, but I find it unlikely that it will be faster
to call a function to return the same value again than just looking
it up in a local variable.
Nonsense. Opera has a long article on their site about this.

Link?

/L
 
D

David Mark

David Mark said:
[second attempt; the first didn't show up for some reason]
On 2008-10-13 20:56, David Mark wrote:
function attachEvents() {
  var el, index = document.getElementsByTagName('div').length;
  while (index--) {
     el = document.getElementsByTagName('div')[index];
Are you suggesting that calling getElementsByTagName() in a loop is more
efficient than storing a reference to the collection in a variable, and
Yes.  Browsers optimize for that pattern.

Do you have any references for that? It sounds likely that they do

I have been trying to dig it up myself.
something smart, but I find it unlikely that it will be faster

Thanks for that, but it is possible that I did something dumb in this
case. The fact that I can't find the article is not encouraging.
to call a function to return the same value again than just looking
it up in a local variable.

Well, define looking it up in a local variable when the local variable
is a reference to a live nodelist? I can't as I don't write browsers.

Not on Opera from what I can see.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top