Are there legitimate uses for JavaScript's "with" statement?

Viewed 70177

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?

33 Answers

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.

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.

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...

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";
});

Hardly seems worth it since you can do the following:

var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";

Visual Basic.NET has a similar With statement. One of the more common ways I use it is to quickly set a number of properties. Instead of:

someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''

, I can write:

With someObject
    .Foo = ''
    .Bar = ''
    .Baz = ''
End With

This isn't just a matter of laziness. It also makes for much more readable code. And unlike JavaScript, it does not suffer from ambiguity, as you have to prefix everything affected by the statement with a . (dot). So, the following two are clearly distinct:

With someObject
    .Foo = ''
End With

vs.

With someObject
    Foo = ''
End With

The former is someObject.Foo; the latter is Foo in the scope outside someObject.

I find that JavaScript's lack of distinction makes it far less useful than Visual Basic's variant, as the risk of ambiguity is too high. Other than that, with is still a powerful idea that can make for better readability.

I think the obvious use is as a shortcut. If you're e.g. initializing an object you simply save typing a lot of "ObjectName." Kind of like lisp's "with-slots" which lets you write

(with-slots (foo bar) objectname
   "some code that accesses foo and bar"

which is the same as writing

"some code that accesses (slot-value objectname 'foo) and (slot-value objectname 'bar)""

It's more obvious why this is a shortcut then when your language allows "Objectname.foo" but still.

Having experience with Delphi, I would say that using with should be a last-resort size optimization, possibly performed by some kind of javascript minimizer algorithm with access to static code analysis to verify its safety.

The scoping problems you can get into with liberal use of the with statement can be a royal pain in the a** and I wouldn't want anyone to experience a debugging session to figure out what the he.. is going on in your code, only to find out that it captured an object member or the wrong local variable, instead of your global or outer scope variable which you intended.

The VB with statement is better, in that it needs the dots to disambiguate the scoping, but the Delphi with statement is a loaded gun with a hairtrigger, and it looks to me as though the javascript one is similar enough to warrant the same warning.

The with statement can be used to decrease the code size or for private class members, example:

// demo class framework
var Class= function(name, o) {
   var c=function(){};
   if( o.hasOwnProperty("constructor") ) {
       c= o.constructor;
   }
   delete o["constructor"];
   delete o["prototype"];
   c.prototype= {};
   for( var k in o ) c.prototype[k]= o[k];
   c.scope= Class.scope;
   c.scope.Class= c;
   c.Name= name;
   return c;
}
Class.newScope= function() {
    Class.scope= {};
    Class.scope.Scope= Class.scope;
    return Class.scope;
}

// create a new class
with( Class.newScope() ) {
   window.Foo= Class("Foo",{
      test: function() {
          alert( Class.Name );
      }
   });
}
(new Foo()).test();

The with-statement is very usefull if you want to modify the scope, what is necessary for having your own global scope that you can manipulate at runtime. You can put constants on it or certain helper functions often used like e.g. "toUpper", "toLower" or "isNumber", "clipNumber" aso..

About the bad performance I read that often: Scoping a function won't have any impact on the performance, in fact in my FF a scoped function runs faster then an unscoped:

var o={x: 5},r, fnRAW= function(a,b){ return a*b; }, fnScoped, s, e, i;
with( o ) {
    fnScoped= function(a,b){ return a*b; };
}

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnRAW(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnScoped(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

So in the above mentioned way used the with-statement has no negative effect on performance, but a good one as it deceases the code size, what impacts the memory usage on mobile devices.

Using with also makes your code slower in many implementation, as everything now gets wrapped in an extra scope for lookup. There's no legitimate reason for using with in JavaScript.

I think the with-statement can come in handy when converting a template language into JavaScript. For example JST in base2, but I've seen it more often.

I agree one can program this without the with-statement. But because it doesn't give any problems it is a legitimate use.

For some short code pieces, I would like to use the trigonometric functions like sin, cos etc. in degree mode instead of in radiant mode. For this purpose, I use an AngularDegreeobject:

AngularDegree = new function() {
this.CONV = Math.PI / 180;
this.sin = function(x) { return Math.sin( x * this.CONV ) };
this.cos = function(x) { return Math.cos( x * this.CONV ) };
this.tan = function(x) { return Math.tan( x * this.CONV ) };
this.asin = function(x) { return Math.asin( x ) / this.CONV };
this.acos = function(x) { return Math.acos( x ) / this.CONV };
this.atan = function(x) { return Math.atan( x ) / this.CONV };
this.atan2 = function(x,y) { return Math.atan2(x,y) / this.CONV };
};

Then I can use the trigonometric functions in degree mode without further language noise in a with block:

function getAzimut(pol,pos) {
  ...
  var d = pos.lon - pol.lon;
  with(AngularDegree) {
    var z = atan2( sin(d), cos(pol.lat)*tan(pos.lat) - sin(pol.lat)*cos(d) );
    return z;
    }
  }

This means: I use an object as a collection of functions, which I enable in a limited code region for direct access. I find this useful.

I just really don't see how using the with is any more readable than just typing object.member. I don't think it's any less readable, but I don't think it's any more readable either.

Like lassevk said, I can definitely see how using with would be more error prone than just using the very explicit "object.member" syntax.

I think that the usefulness of with can be dependent on how well your code is written. For example, if you're writing code that appears like this:

var sHeader = object.data.header.toString();
var sContent = object.data.content.toString();
var sFooter = object.data.footer.toString();

then you could argue that with will improve the readability of the code by doing this:

var sHeader = null, sContent = null, sFooter = null;
with(object.data) {
    sHeader = header.toString();
    sContent = content.toString();
    sFooter = content.toString();
}

Conversely, it could be argued that you're violating the Law of Demeter, but, then again, maybe not. I digress =).

Above all else, know that Douglas Crockford recommends not using with. I urge you to check out his blog post regarding with and its alternatives here.

using "with" statement with proxy objects

I recently want to write a plugin for babel that enables macros. I would like to have a separate variable namespace that keeps my macro variables, and I can run my macro codes in that space. Also, I want to detect new variables that are defined in the macro codes(Because they are new macros).

First, I choose the vm module, but I found global variables in the vm module like Array, Object, etc. are different from the main program, and I cant implement module and require that be fully compatible with that global objects(Because I cant reconstruct the core modules). In the end, I find the "with" statement.

const runInContext = function(code, context) {
    context.global = context;
    const proxyOfContext = new Proxy(context, { has: () => true });
    let run = new Function(
        "proxyOfContext",
        `
            with(proxyOfContext){
                with(global){
                        ${code}
                }
            }
        `
    );
    return run(proxyOfContext);
};

This proxy object traps search of all variables and says: "yes, I have that variable." and If the proxy object doesn't really have that variable, show its value as undefined.

In this way, if any variable is defined in the macro code with the var statement, I can find it in the context object(like the vm module). But variables that are defined with let or const only available in that time and will not be saved in the context object(the vm module saves them but doesn't expose them).

Performance: Performance of this method is better than vm.runInContext.

safety: If you want to run code in a sandbox, this is not safe in any way, and you must use the vm module. It only provides a new namespace.

with is useful coupled with shorthand object notation when you need to transform object structures from flat to hierarchical. So if you have:

var a = {id: 123, name: 'abc', attr1: 'efg', attr2: 'zxvc', attr3: '4321'};

So instead of:

var b = {
    id: a.id,
    name: a.name
    metadata: {name: a.name, attr1: a.attr1}
    extrastuff: {attr2: a.attr2, attr3: a.attr3}
}

You can simply write:

with (a) {
    var b = {
        id,
        name,
        metadata: {name, attr1}
        extrastuff: {attr2, attr3}
    }
}
Related