DEV Community

Cover image for The forgotten `with` statement — and why you shouldn't use it
Lioness100
Lioness100

Posted on

The forgotten `with` statement — and why you shouldn't use it

The with statement extends the scope chain for a statement.

- MDN Web Docs

The with statement was originally implemented to reduce verbosity and when dealing with long object references. If the object is particularly expensive performance-wise, it will also save the compiler from having to parse it multiple times.

You could do this:

const name = authors[0].ref.data[0].data.name;
const lastName = authors[0].ref.data[0].data.lastName;
const age = authors[0].ref.data[0].data.age;

console.log(name, lastName, age);
Enter fullscreen mode Exit fullscreen mode

Or, you could just do this!

with (authors[0].ref.data[0].data) {
  // every property of the given object
  // is transported to the top of the scope
  // and can be referenced directly
  console.log(name, lastName, age);
}
Enter fullscreen mode Exit fullscreen mode

Here's a more practical example that you might find a use for in everyday work:

with(document) {
  with(documentElement) {
    insertBefore(
      createElement("script"),
      firstChild
    )
    .text = "alert(1)"
  }
}
Enter fullscreen mode Exit fullscreen mode

I personally only found out about this keyword a week a go; probably because it has been excluded from almost all modern code. In ES5, it's even forbidden in strict mode 🤯

Why?

Primarily, it's confusing and can easily lead to bugs. Consider the code below:

const foo = 'bar';
with ({ foo: 'baz' }) {
  console.log(foo);
}
Enter fullscreen mode Exit fullscreen mode

Do you think 'bar' will be logged, or do you think 'baz' will be logged? As you can see, this can lead to readability issues. Consider another segment of code below:

// whether we should show the answer of a riddle
const hidden = true;

with (document) {
  const riddleDisplay = getElementById('riddle');
  const [question, answer] = riddleDisplay.children;

  if (hidden) {
    answer.style.display = 'none';
  }

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Pretty easy to understand, right? Nope! Actually, unbeknownst to both the reader and the writer, hidden would be referencing document.hidden.

Maybe you're paying close attention, are well versed in the Document object, and knew this would happen. But what if you didn't know every property of an object? Maybe it's from an external library, or you missed as a property was assigned to it higher in the file.

If you can't read a program and be confident that you know what it is going to do, you can't have confidence that it is going to work correctly. For this reason, the with statement should be avoided...

- Mr. Crockford

Substitutions

Instead of using the with statement, you could assign the reference to a variable, or use array destructuring!

const author = authors[0].ref.data[0].data;
console.log(author.name, author.lastName, author.age);
Enter fullscreen mode Exit fullscreen mode

Or:

const { name, lastName, age } = authors[0].ref.data[0].data;
console.log(name, lastName, age);
Enter fullscreen mode Exit fullscreen mode

Simple, readable, and without any uncertain implications.

Fun Fact

In my previous blog post, I talked about static Symbol properties and how they can be combined with classes.

One property I didn't mention is Symbol.unscopables, which specify any properties that should not be transported to the highest scope.

const foo = {
  bar: 'baz',
};

object[Symbol.unscopables] = {
  bar: true,
};

with (foo) {
  console.log(bar); // Error - bar is not defined
}
Enter fullscreen mode Exit fullscreen mode

I hope you learned a bit about the with statement! If you have any questions, corrections, or addons, I would love to hear them. Peace ✌

Top comments (10)

Collapse
 
baso53 profile image
Sebastijan Grabar

This seems like a very nice feature in some places. I don't think the arguments (actually, it's just a single argument) against it are justified.

You could have any of these problems in a for loop for example. Does that mean we should abandon the for loop because it COULD lead to readability issues? No. Good tooling prevents you from this kind of problems.

Kotlin seems to do just fine with the "with" statement.

Collapse
 
lioness100 profile image
Lioness100 • Edited

Thanks for the input! Could you elaborate on how you would get the same problem the with statement introduces in a loop, and what "good tooling" can completely prevent it?

P.S. If I'm reading the docs correctly, Kotlin's with has completely different functionality wise than Javascript's.

Collapse
 
baso53 profile image
Sebastijan Grabar
const foo = 'bar';
with ({ foo: 'baz' }) {
  console.log(foo);
}
Enter fullscreen mode Exit fullscreen mode
const foo = 'bar';
for(let foo = 0; foo<5; foo++) {
  console.log(foo); // which foo will be printed?
}

Enter fullscreen mode Exit fullscreen mode

You have the exact same problem here. Yet, no one is stating that you shouldn't use the for loop.

All I'm saying is, not using something just because someone said so is sometimes counter-productive. Frankly, this is the first time I've heard of this feature and I'm a little saddened that it's labeled as discouraged.

When comparing to Koltin, they are very similar. The difference is that in Kotlin, this is going to be only the object passed to the with statement, while in JS, this will (effectively) be set to the enclosing this + the object passed to the with statement.

This is the kind of stuff a linter could easily warn you of.

Thread Thread
 
lioness100 profile image
Lioness100

I agree that the "Which is logged?" question isn't particularly useful to determine whether you should us ethe statement or not, since, when creating multiple scopes, you will always run that risk.

const foo = 'bar';
function test() {
  const foo = 'baz';
  console.log(foo); // here it is again
}
Enter fullscreen mode Exit fullscreen mode

However, the much bigger fish I was talking about is when you accidentally reference something you didn't even know existed in the object you use. This problem is not shared with loops since you explicitly write out all variable names, so I don't think your comparison applies.

And what can a linter do? In the example I showed in the post, the linter wouldn't find anything wrong. The only thing linters do is tell you not to use with.

Collapse
 
madsstoumann profile image
Mads Stoumann

I had forgotten “with”, now I need to forget it again 😂

Collapse
 
lioness100 profile image
Lioness100

😂😂

Collapse
 
foxy4096 profile image
Foxy4096 • Edited

The python programming language also have with, but yes it is really underated.
I have rarely seen the use of with in programs.

Collapse
 
piggov profile image
Bonny Piggov • Edited

beware, docs say: "Warning:Use of the with statement is not recommended, as it may be the source of confusing bugs and compatibility issues."

Collapse
 
benjaminwfox profile image
Ben Fox

Err did you read the article? That's pretty much the whole point 😉

Collapse
 
piggov profile image
Bonny Piggov

Ah 😅 Damn, sorry! Nice article!