Exploring JavaScript - About the with Statement
- javascript
When learning JavaScript, one often hears that eval()
should never be used. However, there is another syntax that is also strongly discouraged, albeit less frequently mentioned: the with
statement. Of course, it is a deprecated syntax and is prohibited in strict mode, rendering it practically obsolete. Nevertheless, it is a part of JavaScript history and is related to newer syntaxes such as Symbol.unscopables
. Therefore, I will explore the with
statement in two articles.
In this article, I will look at how the with
statement emerged, how it is used, what its issues are, and how it can be replaced. The following article will discuss the problems caused by with
and what followed afterward.
1. Emergence of the with Statement
When JavaScript 1.0 was created in 1996, its syntax was based on that of the C language. Constructs like if
, for
, while
, return
, and the use of braces were all influenced by C.
To access the properties of object data types, two additional syntaxes were introduced: one was the for...in
loop influenced by AWK, and the other was the with
statement.
Brandan Eich, who created JavaScript, was working at Netscape at the time, and the addition of the with
statement was requested by the Netscape LiveWire team. It was intended to provide a more convenient way to access object properties.
2. Basic Concept
The basic form of the with
statement is as follows:
with (expression) {
statement
}
The with
statement adds the specified expression to the front of the scope chain during the evaluation of its internal statements.
2.1. Operation
When JavaScript searches for data corresponding to an identifier, it looks for that identifier in the scope chain unless it belongs to a specific object. However, when evaluating identifiers within a with
statement, the prototype chain of the object provided to with
is searched first.
This means that within the body of a with
statement, all properties accessible from the test
object can be used as if they were local variables. Before searching the scope chain to retrieve an identifier's data, it first looks for data in the object provided to with
.
// This can be used like so.
// Direct access to properties of the test object within the with statement.
var test = {
firstName: "John",
lastName: "Doe"
};
with(test) {
console.log(firstName + " " + lastName);
// John Doe
}
Using in
indicates that properties in the prototype chain of the object provided to with
can also be used like local variables.
var parent = {
myName: "witch"
};
var child = Object.create(parent);
with(child) {
console.log(myName);
// witch
}
When accessing methods of the object provided to with
, they are called with this
referring to that object.
var obj = {
toString: function() {
return "It's witch's object";
}
};
with(obj) {
console.log(toString());
// It's witch's object
}
2.2. Purpose
The with
statement was originally created to avoid the hassle of accessing nested objects. For example, it can be used in this manner:
foo.bar.baz.a = 1;
foo.bar.baz.b = 2;
// Using with
with (foo.bar.baz) {
a = 1;
b = 2;
}
2.3. Current Status
Currently, the strict mode that prohibits the use of the with
statement has become almost standard practice. Thus, the with
statement has largely fallen out of use. Although there was never a moment when the with
statement was generally recommended after the very early days of JavaScript, certain tricks were possible.
It is well-known that var
variable declarations have function scope. The following code is widely cited as an example demonstrating this fact.
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 10);
}
// 10 is printed 10 times.
As a result, block-scoped variables let
and const
were introduced in ES6. However, before ES6 became widely adopted, it was reportedly possible to mimic block scope using the with
statement.
for (var i = 0; i < 10; i++) {
with({
temp: i
}) {
setTimeout(function() {
console.log(temp);
}, 10);
}
}
Naturally, this is not recommended. In practice, using the IIFE (Immediately Invoked Function Expression) pattern introduced later was more widely used and safer. Recently, the introduction of Array.prototype.with()
and new array methods suggests that the with
statement is unlikely to be used in the future.
3. Problems with the with Statement
I blame 'with'. So, ex-Borland people at Netscape. And so, ultimately, myself. - Brendan Eich
As is well known, the with
statement is not recommended at all, and it raises an error in strict mode. Let's delve into why the with
statement is problematic.
3.1. Readability
The with
statement makes code difficult to read and unpredictable. Consider the following short code snippet from Douglas Crockford’s "JavaScript: The Good Parts":
with (obj){
a=b;
}
This behaves differently depending on which properties the obj
object has. Breaking this down reveals this functionality:
if (obj.a === undefined) {
a = (obj.b === undefined) ? b : obj.b;
} else {
obj.a = (obj.b === undefined) ? b : obj.b;
}
This is because both a
and b
may be properties of obj
, making it very difficult to interpret correctly.
Moreover, this introduces challenges not only for human readers but also for optimizing compilers, leading to performance issues. Typical JavaScript scopes can be efficiently represented with an internal structure, and variable lookups can be performed quickly. However, when using with
, the search for variables must traverse the object's prototype chain, resulting in decreased performance.
Code where the behavior varies based on the properties of the object provided to with
can easily occur in function parameters as well.
function logit(msg, obj) {
with(obj) {
console.log(msg);
}
}
logit("hello", {
msg: "my object"
}); // "my object"
logit("hello", {}); // "hello"
If the obj
has a msg
property, the console.log
inside the with
statement references obj.msg
rather than the parameter msg
. The with
statement makes it difficult to determine what an identifier references beforehand; this can only be accurately understood during runtime.
3.2. Code Vulnerability
The with
statement can introduce vulnerabilities in code. Brendan Eich also cited the challenges of program analysis, rather than performance issues, as a reason for deprecating with
. Axel Rauschmayer, in "Speaking JavaScript," presents the following example code:
function foo(someArray) {
var values=...;
with (someArray) {
values.someMethod(...);
// subsequent code...
}
}
foo(myArray);
Even without accessing myArray
, it can disrupt function calls if a method is added to Array.prototype
.
Array.prototype.values = function() {
// new code...
};
Now, the body of the with
block inside the foo
function will reference someArray.values
instead of the previously defined values
, causing the call to potentially invoke the user-defined Array.prototype.values
. Thus, the with
statement can introduce vulnerabilities in code.
This is not mere speculation; it has previously caused actual bugs in Firefox. This will be discussed further in the next article.
3.3. Inability to Minify Code
The with
statement interferes with code minification tools. These tools optimize code by shortening variable names, and the presence of with
prevents renaming variables, as it can only be determined at runtime whether a name refers to a variable or a property of the with
target object.
If code cannot be minified, its size increases and performance suffers. Moreover, simply increasing the size of the code can be problematic, but declaring common variable names such as values
inside a with
statement can expose the code to further vulnerabilities. This can contribute to some of the issues that will be discussed in the next article.
4. Alternatives to the with Statement
So, what alternatives exist for the with
statement, which is not recommended? Nowadays, as the with
statement is rarely used, perhaps "alternatives" is not the most fitting term; however, the original purpose of the with
statement can be performed in the following way.
When accessing properties of a complex nested object, it is advisable to use temporary variables:
var b = foo.bar.baz;
b.a = 1;
b.b = 2;
If you dislike creating a temporary variable in the current scope, the IIFE pattern can be employed:
(function() {
var b = foo.bar.baz;
b.a = 1;
b.b = 2;
})();
// Alternatively, IIFE can be used with parameters
(function(b) {
b.a = 1;
b.b = 2;
})(foo.bar.baz);
IIFE can also be used to replicate block scoping previously imitated with with
:
for (var i = 0; i < 10; i++) {
(function(temp) {
setTimeout(function() {
console.log(temp);
}, 10);
})(i);
}
References
Allen Wirfs-Brock, Brandan Eich, "JavaScript: the first 20 years", pp. 11-12
Axel Rauschmayer, "Speaking JavaScript", translated by Han Sunyong, Hanbit Media, pp. 244-248
Douglas Crockford, translated by Kim Myoungshin, "JavaScript: The Good Parts", Hanbit Media
David Herman, translated by Kim Junki, "Effective JavaScript", Insight
TYPO3 compatibility regression in Nightly
https://bugzilla.mozilla.org/show_bug.cgi?id=883914#c13
DCU Bank fails to display any accounts on "Accounts" page in Nightly
https://bugzilla.mozilla.org/show_bug.cgi?id=881782
Array.prototype.values() compatibility hazard
https://esdiscuss.org/topic/array-prototype-values-compatibility-hazard
MDN Web docs, "with"
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
Are there legitimate uses for JavaScript's "with" statement?
https://stackoverflow.com/questions/61552/are-there-legitimate-uses-for-javascripts-with-statement
JavaScript’s with statement and why it’s deprecated