Are there legitimate uses for JavaScript’s “with” statement?

Are there legitimate uses for JavaScript’s “with” statement?

Alan Storm’s comments in response to my answer regarding the with statement got me thinking. I’ve seldom found a reason to use this particular language feature, and had never given much thought to how it might cause trouble. Now, I’m curious as to how I might make effective use of with, while avoiding its pitfalls.
Where have you found the with statement useful?

Solutions/Answers:

Solution 1:

Another use occurred to me today, so I searched the web excitedly and found an existing mention of it: Defining Variables inside Block Scope.

Background

JavaScript, in spite of its superficial resemblance to C and C++, does not scope variables to the block they are defined in:

var name = "Joe";
if ( true )
{
   var name = "Jack";
}
// name now contains "Jack"

Declaring a closure in a loop is a common task where this can lead to errors:

for (var i=0; i<3; ++i)
{
   var num = i;
   setTimeout(function() { alert(num); }, 10);
}

Because the for loop does not introduce a new scope, the same num – with a value of 2 – will be shared by all three functions.

A new scope: let and with

With the introduction of the let statement in ES6, it becomes easy to introduce a new scope when necessary to avoid these problems:

// variables introduced in this statement 
// are scoped to each iteration of the loop
for (let i=0; i<3; ++i)
{
   setTimeout(function() { alert(i); }, 10);
}

Or even:

for (var i=0; i<3; ++i)
{
   // variables introduced in this statement 
   // are scoped to the block containing it.
   let num = i;
   setTimeout(function() { alert(num); }, 10);
}

Until ES6 is universally available, this use remains limited to the newest browsers and developers willing to use transpilers. However, we can easily simulate this behavior using with:

for (var i=0; i<3; ++i)
{
   // object members introduced in this statement 
   // are scoped to the block following it.
   with ({num: i})
   {
      setTimeout(function() { alert(num); }, 10);
   }
}

The loop now works as intended, creating three separate variables with values from 0 to 2. Note that variables declared within the block are not scoped to it, unlike the behavior of blocks in C++ (in C, variables must be declared at the start of a block, so in a way it is similar). This behavior is actually quite similar to a let block syntax introduced in earlier versions of Mozilla browsers, but not widely adopted elsewhere.

Solution 2:

I have been using the with statement as a simple form of scoped import. Let’s say you have a markup builder of some sort. Rather than writing:

markupbuilder.div(
  markupbuilder.p('Hi! I am a paragraph!',
    markupbuilder.span('I am a span inside a paragraph')
  )
)

You could instead write:

with(markupbuilder){
  div(
    p('Hi! I am a paragraph!',
      span('I am a span inside a paragraph')
    )
  )
}

For this use case, I am not doing any assignment, so I don’t have the ambiguity problem associated with that.

Solution 3:

As my previous comments indicated, I don’t think you can use with safely no matter how tempting it might be in any given situation. Since the issue isn’t directly covered here, I’ll repeat it. Consider the following code

user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);

with(user){
    name = 'Bob';
    age  = 20;
}

Without carefully investigating those function calls, there’s no way to tell what the state of your program will be after this code runs. If user.name was already set, it will now be Bob. If it wasn’t set, the global name will be initialized or changed to Bob and the user object will remain without a name property.

Bugs happen. If you use with you will eventually do this and increase the chances your program will fail. Worse, you may encounter working code that sets a global in the with block, either deliberately or through the author not knowing about this quirk of the construct. It’s a lot like encountering fall through on a switch, you have no idea if the author intended this and there’s no way to know if “fixing” the code will introduce a regression.

Modern programming languages are chocked full of features. Some features, after years of use, are discovered to be bad, and should be avoided. Javascript’s with is one of them.

Solution 4:

I actually found the with statement to be incredibly useful recently. This technique never really occurred to me until I started my current project – a command line console written in JavaScript. I was trying to emulate the Firebug/WebKit console APIs where special commands can be entered into the console but they don’t override any variables in the global scope. I thought of this when trying to overcome a problem I mentioned in the comments to Shog9’s excellent answer.

To achieve this effect, I used two with statements to “layer” a scope behind the global scope:

with (consoleCommands) {
    with (window) {
        eval(expression); 
    }
}

The great thing about this technique is that, aside from the performance disadvantages, it doesn’t suffer the usual fears of the with statement, because we’re evaluating in the global scope anyway – there’s no danger of variables outside our pseudo-scope from being modified.

I was inspired to post this answer when, to my surprise, I managed to find the same technique used elsewhere – the Chromium source code!

InjectedScript._evaluateOn = function(evalFunction, object, expression) {
    InjectedScript._ensureCommandLineAPIInstalled();
    // Surround the expression in with statements to inject our command line API so that
    // the window object properties still take more precedent than our API functions.
    expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
    return evalFunction.call(object, expression);
}

EDIT: Just checked the Firebug source, they chain 4 with statements together for even more layers. Crazy!

const evalScript = "with (__win__.__scope__.vars) { with (__win__.__scope__.api) { with (__win__.__scope__.userVars) { with (__win__) {" +
    "try {" +
        "__win__.__scope__.callback(eval(__win__.__scope__.expr));" +
    "} catch (exc) {" +
        "__win__.__scope__.callback(exc, true);" +
    "}" +
"}}}}";

Solution 5:

Yes, yes and yes. There is a very legitimate use. Watch:

with (document.getElementById("blah").style) {
    background = "black";
    color = "blue";
    border = "1px solid green";
}

Basically any other DOM or CSS hooks are fantastic uses of with. It’s not like “CloneNode” will be undefined and go back to the global scope unless you went out of your way and decided to make it possible.

Crockford’s speed complaint is that a new context is created by with. Contexts are generally expensive. I agree. But if you just created a div and don’t have some framework on hand for setting your css and need to set up 15 or so CSS properties by hand, then creating a context will probably be cheaper then variable creation and 15 dereferences:

var element = document.createElement("div"),
    elementStyle = element.style;

elementStyle.fontWeight = "bold";
elementStyle.fontSize = "1.5em";
elementStyle.color = "#55d";
elementStyle.marginLeft = "2px";

etc…

Solution 6:

You can define a small helper function to provide the benefits of with without the ambiguity:

var with_ = function (obj, func) { func (obj); };

with_ (object_name_here, function (_)
{
    _.a = "foo";
    _.b = "bar";
});