2011-06-01

JavaScript’s with statement and why it’s deprecated

This post explains how the with statement works in JavaScript and why its use is discouraged.

Syntax and semantics

Syntax:
    with (object)
        statement
introduces the properties of object as local variables in statement. Example (the braces are optional for single statements, but it is recommended to add them):
    > with({ first: "John" }) { console.log("Hello "+first); }
    Hello John
Its intended use is to avoid redundancy when accessing an object several times.
    foo.bar.baz.bla   = 123;
    foo.bar.baz.yadda = "abc";
with turns this into:
    with(foo.bar.baz) {
        bla   = 123;
        yadda = "abc";
    }
There is one similar case in JavaScript where the properties of an object are turned into variables and that is the global object window. All of its properties are global variables and vice versa. While new global variables are automatically added to window, the with statement works differently: variables that are declared inside it are not added to its argument, but to the surrounding function, where they still exist after leaving it.
    > with({}) { var x = "abc"; }
    > x
    'abc'

The with statement is deprecated

The use of the with statement is generally discouraged. It is forbidden in strict mode:
    > function foo() { "use strict"; with({}); }
    SyntaxError: strict mode code may not contain 'with' statements
Best practice: Don’t use a with statement.
    with(foo.bar.baz) {
        console.log("Hello "+first+" "+last);
    }
Do use a temporary variable with a short name.
    var b = foo.bar.baz;
    console.log("Hello "+b.first+" "+b.last);
If you don’t want to expose the temporary variable b to the current scope, you can use an IIFE:
    (function() {
        var b = foo.bar.baz;
        console.log("Hello "+b.first+" "+b.last);
    }());
You also have the option of making the object that you want to access a parameter of the IIFE:
    (function(b) {
        console.log("Hello "+b.first+" "+b.last);
    }(foo.bar.baz));

The rationale of the deprecation

To understand why with is deprecated, look at the following example and notice how the function’s argument completely changes how it works.
    function foo(arg) {
        with(arg) {
            console.log("arg: "+arg)
        }
    }
Interaction:
    > foo("Hello");
    arg: Hello  // parameter arg
    
    > foo({});
    arg: [object Object]  // parameter arg
    
    > foo({ arg: "Hello" });
    arg: Hello  // property arg.arg
There are thus two problems that the with statement causes:
  • Performance: one cannot optimize the access to arg (or to any other variable used inside with), because one cannot predict whether arg will refer to a real variable or to a property inside the with argument. That can change with each call.
  • Security: you cannot determine what an identifier refers to by looking at its syntactic surroundings (its lexical environment). According to Brendan Eich that was the actual reason why with was deprecated, not performance considerations. Quoting a tweet of his:
    with violates lexical scope, making program analysis (e.g. for security) hard to infeasible.

4 comments:

Oliver said...

I'd say that the ambiguity of the "with" statement is known to many developers who have been working with JS a bit longer, and while I never came into a situation where I felt like I wanted/had to use it, others at least seem to see proper use cases: http://webreflection.blogspot.com/2009/12/with-worlds-most-misunderstood.html. It would be interesting to know how often it is actually being used out there. But I guess many developers won't really miss it.http://webreflection.blogspot.com/2009/12/with-worlds-most-misunderstood.html. It would be interesting to know how often it is actually being used out there. But I guess many developers won't really miss it.

Axel Rauschmayer said...

Thanks, good link.

Lisa John said...

This is very helpful article and i see there are many informative articles on java. Thanks and i am sharing this with my friends! Sample Statements

Triynko said...

It's a bad decision to deprecate the with statement. Other EMCAScript dialects like ActionScript 3 support it, and deprecating it will make porting from AS3 to JavaScript difficult. The "arg" example is contrived. Rarely if ever would one have an argument named "arg" that itself has a property named "arg". If argument "arg" didn't have a property named "arg", then referencing "arg" in the with statement would resolve to the argument "arg" as expected, so it's not as confusing as it looks anyway.

One example where I reuse code is with a "translated" version of a lesson page in an online tutoring system. The "Spanish content" exists on the same page (same frame) in the Flash animation and has all the same instance names as the English, but it's wrapped in its own display object instance so it can be hidden at first, displayed later, and inside the wrapper it preserves all the instance names so they match the English ones. This makes it easy to re-use the code that references those instances by simply wrapping a "with (spanishContentWrapper)" statement around the copy of the original code. The copied code will of course have some modifications specific to the Spanish text, but the instance names will not have to change as a result of using the "with" statement. It's a time-saver and a perfectly legitimate use that is far less confusing than any other alternative.

Web Analytics