Javascript is asynchronous by nature. Your code can't wait. Instead, you say, "when this happens, call this function". This means that Javascript code is full of functions. Functions that are passed to other functions, called by them, and sometimes even returned by them to be called later. These functions often want to use some data that their defining scope has available, but their caller (the browser) does not.
function getSize(src, callback){
var img = new Image();
img.onload = function(event){
console.log(event);
callback(img.width, img.height);
}
img.src = src
}
Now, this looks like a normal code. Except it uses the closure scope! img
and callback
are both closed over by img.onload
. Now, imagine you didn't have closures. What are the other options? You might find img
in the event object, but definitely not the callback
. You need a function that takes two kinds of arguments: those that are set when you define a function, and those that are set when you call it. Or, you can tell the system everything you need. Or, you need a native currying mechanism*. You don't want to tell foreign code everything you know just because you need to react to its events, so let's assume that such mechanism exists. ES5 introduces bind
:
* currying is a technique where a new function is created by binding arguments to an existing function in advance, and sending the arguments as the remaining arguments to the original function. The original function's return value is then returned
function getSize(src, callback){
var img = new Image();
img.onload = function(callback, img, event){
callback(img.width, img.height);
}.bind(null, callback, img); //not needed if you have closures
img.src = src
}
What are the arguments to bind
? null
sets the this
context. Our hypothetical curry
method would not. Then, callback
and img
. These must be in the same order as the arguments to img.onload
. This is acceptable every now and then, but not if you have to curry uphill in both directions. More importantly, it is acceptable if you have three arguments, but not if you have thirty.
Now, for the funny part: The global scope is technically a closure scope too. But, even if it wasn't, you still don't want to define your module's utility functions in the global scope (namespacing issues). Functions are stored in variables!
getSize = function(Image, src, callback){
var img = new Image();
img.onload = function(callback, img, event){
callback(img.width, img.height);
}.bind(null, callback, img); //not needed if you have closures
img.src = src
}.bind(null, Image) //not needed if you have closures
Now, assume you want to transform the image coordinates for some reason, and that transformation is dependent on the source. Where do we need to add transform
and src
?
getSize = function(Image, transform, src, callback){ //here
var img = new Image();
img.onload = function(transform, src, callback, img, event){ //here
var coord = transform(img.width, img.height, src); //used here
callback(coord.x, coord.y);
}.bind(null, transform, src, callback, img); //here
img.src = src;
}.bind(null, transform, Image); //here
Can you spot the bug? Yep, the outer bind arguments are in the wrong order.
You can circumvent this by holding all parameters in a global object. Of course, you don't want to modify the caller's scope, so you define your own scope whenever you want to pass a few extra arguments. With a few good conventions, this may be reasonably bug-free:
var scope = {Image: Image, ...}
getSize = function(scope, src, callback){
var img = new scope.Image();
var scope = {scope: scope, src: src, callback: callback, img:img};
img.onload = function(scope, event){
var coord = scope.transform(scope.img.width, scope.img.height, scope.src);
callback(coord.x, coord.y);
}.bind(null, scope);
img.src = src;
}.bind(null, scope)
Can you spot the bug now? scope.transform
is undefined
. We need scope.scope.transform
There's another one. Can you spot it too? Now this is getting ridiculous, even if we used one-letter variable for scope
here. Maybe we could utilise the prototype chain:
var scope = {Image: Image, ...}
getSize = function(scope, src, callback){
var img = new scope.Image();
var scope = scope.Object.create(scope)
scope.src = src;
scope.callback = callback;
scope.img = img;
img.onload = function(scope, event){
var coord = scope.transform(scope.img.width, scope.img.height, scope.src);
callback(coord.x, coord.y);
}.bind(null, scope);
img.src = src;
}.bind(null, scope)
of course, we could define our local variables directly in the scope so that we don't have to declare them twice if some inner function might use it. Unfortunately, there's no such trick for the arguments. There is the arguments
variable, but using it for our purpose is kinda messy (scope.getSizeArgs[0]
; quick - which one is it?). However, so far, to circumvent the lack of closures we have:
- for each scope, created a special
scope
variable, with the parent's scope as its prototype.
- added all arguments to it
- used it for all local variables that might be needed in an inner function.
Of course, this is getting ridiculous. Maybe if the language somehow knew that if we use an undeclared variable, it should search for it in the defining scope, and even create such scope
object for us when needed so that the variables can outlive their defining functions? Well, it does.
function getSize(src, callback){
var img = new Image();
img.onload = function(event);
console.log(event);
var coords = transform(img.width, img.height);
callback(coords.x, coords.y);
}
img.src = src;
}
Also, this explicit passing has another disadvantage: whenever you write to the scope, you always write to a local variable. You cannot modify a variable from an inner function. Sure, you could use one-element arrays or a special Reference<T>
type, but this smells of a non-awesome language. With closures, you can do this directly. Do you actually need this?
var width = window.clientWidth;
window.addEventListener('resize', function(){width = window.clientWidth});
setInterval(function(){
...
You might say that sometimes you want the caller's scope as the alternative would be to have 30 arguments. But, the caller does not want you to see their local scope, and they don't want to have to avoid variable names you might one day consider significant. You can still pass objects around, but you don't call them scopes. Some may say "data transfer object"; I prefer the term "named arguments"
$.ajax({
url: "http://www.example.com/cors-api/echo.json",
dataType: "json",
success: function(response){
..
Now, for the classic example of closure scoping, complete with an immediately invoked function expression to limit the scope of the static variable nextId
:
var getId = (function(){
var nextId = 0;
return function getId(){ //named for clarity
return nextId++;
}
})()
getId() //0
getId() //1
getId() //2