I was trying to do a bit of caching of some data asynchronously.I had "neededEndpoints" that was a hash of the endpoints I needed to hit and store the data for in another hash, call it cache
So here was my first attempt: (it didn't look so blatantly wrong when I coded it, I've stripped out stuff in the name of simplicity)
for(var endpoint in neededEndpoints){
jsonGet("/rest/"+endpoint, function(res){
cache[endpoint] = res;
});
}
The trouble with this code is that javascript is not block scoped like some other C-like languages, and so "endpoint" is effectively passed by reference, not by value, and the last value of "endpoint" was used for all the cache storing.
I remembered in previous work we'd done here, we had a CreateDelegate function:
function CreateDelegate(scope, fn) {
return function () {
if(fn != undefined){
fn.apply(scope, arguments);
}
};
}
Usually we'd call it with "this" as the argument for scope, but I wasn't getting the results I expected. Googling I found this page that gave me the nudge I needed to come up with this:
for(var endpoint in this.neededEndpoints){
jsonGet("/rest/"+endpoint,
CreateDelegate({"ep":endpoint}, function(res){
cache[this.ep] = res;
})
);
}
So what's going on? The mental model I've come up with says CreateDelegate is in effect making a snapshot of the current state of things-- that's what a delegate is, in effect. It's actually the act of instantiation of CreateDelegate that makes the snapshot, and then the code inside it can have exactly the context we give it.
When you create a closure in JS (and most imperative languages where you can create closures), that closure has as its context the scope that it was created in. So code like this:
ReplyDeletevar x = 5;
(function() { x = 12; })();
console.log(x);
prints 12.
Now, as for the Javascript "this" pointer, it is insane. It is always bound at the time it is CALLED, never at the time it is defined. So you have to be sure, when you pass functions around, that the code you are passing them to calls it "correctly", or else terrible things happen.
ReplyDeletevar func = function() { console.log(this); };
var x = {};
var y = {method:func};
func(); // "this" is the "global" object (generally window)
new func(); // "this" is a newly constructed object, which this expression returns
y.method(); // "this" is y
func.apply(x); // "this" is x
CreateDelegate is simply a way of making sure that no matter how the code you don't control calls the function, the "this" pointer always points at the same thing. You can pass in any random javascript object you like.
Thanks for clarifying Jeremy! I almost want to see an article "Use of 'this' Considered Harmful?"
ReplyDelete