Playing with JavaScript variable lookup

written by Martin Häcker on

I am always amazed at the very nice features of JavaScript on the one hand and the very, very bad features of it on the other side.

Here's something I learned the other week that I find quite interesting: eval vs. new Function. Here's what I wanted to achieve: I was looking for a way to do some meta-programming with JavaScrip, specifically prevent the problem that any variable you assign to but don't declare ends up as a member of the global object.

The problem is that name lookup in JavaScript is quite peculiar. First it consults the current function activation for local variables and then it goes straight back to the window object and just prepends all it's contents to the local namespace.

Doh. This has the very bad consequence that every variable you forget to declare via the var someVariableName syntax becomes part of the global object - and therefore itself global.

Now you can change this lookup by inserting some of your objects in this lookup chain by using the somewhat controversial with statement like so:

var namespace = {foo:'bar'};
with (namespace) {
    // this scope now has a local variable foo with the value bar
}

This is considered a bad feature, as it means that if you assign to foo that will assign a new value to namespace but if you assign to something else or mistype, that will still end up on the global object. Not very nice - and therefore most JavaScript programmers don't use with ever.

Still with some working and eval it can be used to re-map free functions transparently, so that you can do something like this:

var namespace = {
    equals: function(actual, expected){
        if (actual !== expected)
            console.log('actual', actual, 'does not equal expected', expected);
    }
}

function test(aDescription, aTestFunction) {
    useNamespaceForFunction(namespace , aTestFunction)();
}

test("that I can call equals as a free function", function(){
    equals(1,1);
});

Which would allow you to not pollute the global namespace with all the testing equality functions but still call them in a convenient way (that is without needing to go through an object.

All in all, quite nice really - even though I haven't really found a use for this yet. :-)

Implementing the useNamespaceForFunction however isn't quite as straightforward as I had thought - here's my first go at it:

function useNamespaceForFunction(aNamespace, aFunction) {
    with (aNamespace) {
        return  eval('(' + aFunction + ')');
    }
}

I have since learned that actually using new Function to do the eval might be quite a good idea as the whole point of it is that it ignores the namespace around it, so here's my version two:

function useNamespaceForFunction(aNamespace, aFunction) {
    var namespacingCode = "with (aNamespace) { return (" + aFunction + "); }";
    // using new Function instead of eval to prevent the current namespace leaking into the eval<
    return new Function("aNamespace", namespacingCode)(aNamespace);
}

So, lets see what uses come up for this technique - I've seen some js code that does an Interpreter of sorts with this trick - but thats about it.

So - I didn't achieve my initial goal - but I did come nearer to it. So I'll call it a success here for now.

If you find any use for this technique, please let me know!

[browser:open-source/javascript-hacks/trunk/namespacing-functions.js Get the source!]