This is a valid criticism, and if you wanted to really make the case you'd say I think, "think about every time you have ever written an anonymous class. That anonymous class was probably an ugly closure." So Java programmers basically already know that if you want to list your MP3s you might have to write:

String[] mp3s = new File("/home/drostie/Music").list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.length > 4) && (
name.substring(name.length - 4).toLowerCase() === ".mp3"
);
}
});

In Node.js this becomes:

var mp3s = fs.readdirSync("/home/drostie/Music").filter(function (name) {
return name.slice(-4).toLowerCase() === ".mp3";
});

But as you said, this doesn't use the environment. The question is, when do you really need the environment?

One answer is that it's usually important when that environment is changing and you're modifying that environment; then you have to worry, "Am I modifying the right value? Do I have the right value?" And that's a much more difficult case. MIT's Abelson-Sussman lectures are very clear when talking with Scheme about assignment. "Why do you care about assignment? Because sometimes you want new Date(), or Math.random(), or any number of other constructs which don't return the same thing when you call them twice in a row."

There are some simple use cases for this, like tracking how much work you're doing. In client-side JavaScript you might write something like this:

var el_text_area = document.getElementById("wordbox"),
el_charcount = document.getElementById("charcount"),
clock;

el_text_area.onblur = function () {
// stop updating when the user isn't typing.
if (clock !== undefined) {
clearInterval(clock);
}
};

el_text_area.onfocus = function () {
el_text_area.onblur(); //clear the clock if it somehow already exists.
clock = setInterval(function () {
el_charcount.value = el_text_area.value.length;
}, 50);
};

All of those external variables are being dynamically modified.

The last case where I really used closures was as a debugging tool; I wrote a function which would take as input a function and 'wrap' it up for tracing its execution. Here it is in its full gory detail:

function log_call(fn) {
var count = 0;
return function () {
var out, name = fn.name + "#" + (count += 1);
function log(verb, value) {
console.log(name + " " + verb + " ", value);
}
log("<--", [].slice.call(arguments, 0));
try {
out = fn.apply(this, arguments);
log("-->", out);
return out;
} catch (e) {
log("--X", e);
throw e;
}
};
}

I use it by writing code like this:

function fibs(n) {
return n <= 1 ? n : fibs(n - 2) + fibs(n - 1);
}
// fibs seems slow, let's see what's going on here
fibs = log_call(fibs);

//later in the code
fibs(22);

The above code is in fact horribly inefficient, and gets to fibs#57313 before it starts to construct the Fibonacci numbers. The debug trace shows all of this, if you run it.

We can solve that by now defining a caching decorator:

function cached_onearg(fn) {
var cache = {};
return function (x) {
var out;
if (cache.hasOwnProperty(x)) {
return cache[x];
} else {
return (cache[x] = fn.call(this, x));
}
};
}
fibs = log_call(cached_onearg(function (n) {
return n <= 1 ? n : fibs(n - 2) + fibs(n - 1);
));

The logger now shows a much more reasonable 43 calls.