j201's
Blather

Evil JS: With Considered Somewhat Useful

Note: Do not try this at work. It's not that bad but your coworkers and Douglas Crockford might get cross.

with statements are a little-used, oft-reviled, and underappreciated part of JavaScript. Basically, they allow you to write a statement, often a block, with the properties of a given object added to the scope. Here's an example:

var x = Math.cos(3/2 * Math.PI);

with(Math) {
    var y = cos(3/2 * PI);
}

x; // -1
y; // -1

The thing is, with statements are almost universally renounced in the JavaScript community. For example, you can read Douglas Crockford's attack on with from 2006 here. First of all, with can slow code down by making it difficult for the engine to know what variable is being referred to. But the main problem with with is that it complicates JS's notion of scope. Without with, the variables available in a scope are all of the global variables plus any variables made in local scopes using var or function statements. All of these variables can be both accessed and modified. But using with adds variables to the local scope that were not declared with a var or function statement and shadow those that were. Here's an example of the confusion that can be caused:

var obj = {
    a : 1,
    b : 2
};

with (obj) {
    a = 3;
    var b = 4;
    b = 5;
    c = 6;
}

Now, what are the values of obj.a, obj.b, obj.c, a, b, and c? ANSWER: obj is {a : 3, b : 5}, a isn't defined, b is 4, and c is 6.

So there are good reasons to avoid with. In fact, ES5's strict mode prohibits its use. But the level of hatred and fear directed at it isn't proportional to its flaws and ignores the legitimate uses of with, which I'll cover now.

Libraries and Modules

To use a library in JS, one generally has to constantly refer to its object when using its functions, for example using jQuery.ajax instead of ajax. This has led JS libraries to adopt short names such as goog, _, or $ for somewhat easier typing, but with the costs of poor readability and losing useful short local variable names. Not adding the library functions to the scope is fine for libraries that you aren't using much, but can be inconvenient for libraries you're using heavily, which is why most programming languages provide a way to import the functions of a module into the current scope. Well, JS has one too:

// Returns a random angle in radians from a circle divided into the given number of steps

function randomAngle(steps) {
    with(Math) {
        if (!steps)
            return random() * 2 * PI;
        else
            return floor(random() * steps) / steps * 2 * PI;
    }
}

function randomAngle2(steps) {
    if (!steps)
        return Math.random() * 2 * Math.PI;
    else
        return Math.floor(Math.random() * steps) / steps * 2 * Math.PI;
}

Yes, it's the same Math example. But it shows an important point: with can make dealing with libraries and built-in modules a lot easier. Unfortunately, with current attitudes towards with, we're stuck waiting until ES6 for a (hopefully) accepted way to do this.

Block Scope

One oddity that makes JavaScript different from most C-style languages is the lack of block scope. Instead of local variables beings scoped to nearest block they're declared in (delimited by { and }), like in C or Java, JS variables are scoped to the nearest function() {...} in which they are declared. This is mostly fine (and in my opinion, not a problem at all if you use higher order array iterators), but can be occasionally problematic.

A common issue in asynchronous JS is using callbacks in a loop:

for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, 10);
}

// From console:
// 5
// 5
// 5
// 5
// 5

So, since the for loop finished executing before the callbacks were executed, the value of i is 5 every time. In other languages, to get around this, you'd just add a block-scoped variable for each iteration of the loop. In fact, this can be done right now in Firefox, but won't be standard until ES6 (see Solution 1 below). So, the standard solution is to wrap the whole thing in an IIFE (see Solution 2) which is widely supported, but adds a lot of visual noise. The other solution is to use with to emulate a block scope (Solution 3):

// Solution 1 - Elegant, but not widely supported
for (var i = 0; i < 5; i++) {
    let j = i; // A block scoped variable
    setTimeout(function() { console.log(j); }, 10);
}

// Solution 2 - Idiomatic but ugly
for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() { console.log(j); }, 10);
    })(i);
}

// Solution 3 - Widely supported and readable
for (var i = 0; i < 5; i++) {
    with ({j : i})
        setTimeout(function() { console.log(j); }, 10);
}

// From console:
// 0
// 1
// 2
// 3
// 4

Basically, with lets you make block scoped variables. In fact, it's very similar to the let blocks that are coming in ES6:

var a;

let (b = 2, c = 3) {
    a = b + c;
}

a; // 5

with ({b : 4, c : 7}) {
    a = b + c;
}

a; // 11

So, while function scoped variables are usually adequate, with lets you use block scoping when you need it.

Summary

Pros:

Cons:

Conclusion

I've identified two cases where with can make for clearer code and emulate features that exist in most other languages. However, the JS community's aversion to with makes it almost unusable except in personal projects. Fortunately, both of its use cases will be replaced in ES6 by let and import, but for now, many coders are depriving themselves of a useful tool.So, don't use it at work, but if you have some hobby coding where readability is more important than speed, don't be too afraid to use with.

comments powered by Disqus