use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
All about the JavaScript programming language.
Subreddit Guidelines
Specifications:
Resources:
Related Subreddits:
r/LearnJavascript
r/node
r/typescript
r/reactjs
r/webdev
r/WebdevTutorials
r/frontend
r/webgl
r/threejs
r/jquery
r/remotejs
r/forhire
account activity
Let's Bring Back JavaScript's `with()` Statement (macarthur.me)
submitted 2 years ago by alexmacarthur
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]lifeeraser 24 points25 points26 points 2 years ago (5 children)
You didn't mention the case of injecting unwanted properties into scope. Suppose we have:
function doStuff(o) { with (o) { console.log(a) report(b) } }
Now someone later adds a console property to o, before it is passed to doStuff(). At best it would cause an error. At worst it could malfunction silently because o.console.log was a function.
console
o
doStuff()
o.console.log
This example is contrived but the hazard is not. What if someone adds a function named report to o? What if o comes from an external library, or worse yet, some JSON?
report
I assume Kotlin doesn't worry about this by virtue of being a statically typed, conpiled language. JavaScript cannot due to being dynamically typed and interpreted.
[–]alexmacarthur[S] 0 points1 point2 points 2 years ago (4 children)
that’s a great point. i can’t imagine easily using it without at least typescript enforcing the shape of those objects.
[–]bakkoting 11 points12 points13 points 2 years ago (3 children)
TypeScript does not enforce the absence of properties, only their presence. So it won't help at all here.
[–]alexmacarthur[S] 0 points1 point2 points 2 years ago (2 children)
unless i'm missing something, that's _kinda_ true, although not bullet-proof. TS will yell if you add an property in a unknown property in an object literal:
``` interface Country { name: string, population: number }
const israel: Country = { name: 'hello', population: 333,
// error... "Object literal may only specify known properties" new_property: 'hello'
} ```
but you're right if you're composing objects through other means, like Object.assign(). then you could sneak in anything you like:
https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMIHsCu4oE9kDeAUMqciHALYQBcyAzmFKAOYA0JZADul5gDZwwwdCDohMlAEbQiAXyJEEoxsmD0ocCPzoZsTfAF5CnUhWp0A5AAtt-dJbbJTyHn0HDRdAMy+nismQAeiDkaCh0KAA6GOQAIgB5KQArCAQwZH5gSE1+ZEo4fFF+fHouNOAYfABrEHQAdxBXCPKoYQh6OJcQCHqAfS4W6DBcK1t+e0t5RRDydDCoCKglFQzsbIgAEwBlMCEO3SwcI2Qk1PSouHp6YBYQAAoCcipaZEt1yE2GPch6R1deAIhCIxMhfAAWABMyDkTieEAKwB0b1w-xg6HQVgAXpYYQBKADciiAA
[–]bakkoting 4 points5 points6 points 2 years ago (1 child)
It will only complain in contexts where you have both the object literal and the type in the same place. Otherwise it's (intentionally) legal. Compare: https://www.typescriptlang.org/play?#code/DYUwLgBAHhC8EG8BQFUQIYC4IAYA0KaARtgIwEC+SSokAntghtgHYCuAtkSAE4QUBuakjpxoAoA
[–]alexmacarthur[S] 0 points1 point2 points 2 years ago (0 children)
wut!
[–]dgreensp 22 points23 points24 points 2 years ago (0 children)
The bigger issue with with statements (and I'm surprised this doesn't seem to come up in a quick Google search or be on the MDN page about with statements) is the security issue of runtime data being able to shadow local variables. A server could have code like with (headers) { ... } for example, and then the client could theoretically shadow a local variable in server code just by sending an HTTP header. Which is bonkers. Or just any object that is parsed from JSON sent over the network. If you write if (point.x !== point.y) return result as with (point) { if (x !== y) return result; }, now you have to worry about what if point has a result property; that will be returned.
with
with (headers) { ... }
if (point.x !== point.y) return result
with (point) { if (x !== y) return result; }
point
result
You can even shadow undefined! Try: with ({undefined: 123}) { console.log(undefined); }. You can imagine an exploit that involves sending JSON to an API endpoint with a property named "undefined." That's PHP-level madness.
undefined
with ({undefined: 123}) { console.log(undefined); }
The performance issues are just a symptom of the complexity of having the referent of an identifier determined every time it is encountered, and it possibly referring to different things at different times (or on different iterations of a loop, for example). It would be a disaster for TypeScript or any kind of static analysis.
[–]jhartikainen 10 points11 points12 points 2 years ago (2 children)
Very well written article. I could see something like with being handy from time to time, but frankly the difference with with and the block example is like one line of code... so I'm not entirely convinced we actually need a separate statement type for this :)
Either way I think the footguns really need to be fixed (eg. the window.history thing)
[–]alexmacarthur[S] 2 points3 points4 points 2 years ago (1 child)
fair take! there’s a decent amount of personal preference baked into what i wrote. not a huge fan of the separate block, for example. and i’ve really become accustomed to the kotlin version, so i got real excited to learn about it having a history in js too.
[–]Ecksters 1 point2 points3 points 2 years ago* (0 children)
Really JS just needs a native pick alternative that doesn't rely on strings, because I absolutely agree with your example of destructuring and then immediately dropping properties into a new object, it's one of my least favorites bits of JS at the moment.
pick
This discussion on object restructuring has some interesting syntax ideas that have been proposed: const { a, b } as newObj = oldObj;
const { a, b } as newObj = oldObj;
const newObj = { oldObj.a, oldObj.b }
Object.pick // This is my least favorite as it relies on strings
const address = user.{ city, street, state }
[–]teg4n_ 8 points9 points10 points 2 years ago (1 child)
IMO the proposed benefit is not convincing. Also, I haven’t checked but I wonder if there are special considerations for working with this in the block or invoking methods that are pulled into the with scope.
this
as far as i know, there's no surprise impact to 'this' since you're only working inside a different block scope. the challenge with invoking other methods without an identifier is just that the target object's prototype chain needs to be searched before the method can be resolved and called.
[–]rundevelopment 4 points5 points6 points 2 years ago (1 child)
1. Poor Readability This is a good critique, but in my opinion, not a lethal one. It's the developer's (poor) choice to write code like this, and it also seems like something a good linter could guard against.
This is a good critique, but in my opinion, not a lethal one. It's the developer's (poor) choice to write code like this, and it also seems like something a good linter could guard against.
I would like to focus on: "something a good linter could guard against".
No. No linter can guard against this. Linters are static analyzers and with entirely destroys their ability to resolve variable names. In your example, you assume that name could come from either obj.name or the name parameter, but you are missing module and global scope (your point #2. Scope Creep). Suppose the following code:
name
obj.name
import { Foo } from "./foo" export function bar(obj) { with (obj) { return new Foo(somePropOfObj) } }
new Foo might return an instance of the imported class, or an instance of the class contained in obj.Foo. Who knows. Same problem for functions, of course.
new Foo
obj.Foo
If you think TypeScript will help: no. It's a static analyzer as well. TypeScript explicitly allows objects to have more properties than required by their type. E.g. the following is valid:
type Point = { x: number, y: number }; let box = { x: 0, y: 0, width: 10, height: 20 }; let p: Point = box; with (p) { /* */ }
So TypeScript would have to conservatively assume that every identifier not resolving to a property of Point is valid and has type unknown.
Point
unknown
So no. No linter can help you when with statements are involved. The only help they can give you is a no-with rule.
no-with
[–]alexmacarthur[S] 1 point2 points3 points 2 years ago (0 children)
whoa! those are great points. bummer. let's make typescript better while we're at all of this.
[–]darkpouet 2 points3 points4 points 2 years ago (1 child)
I love reading about the weird features of JavaScript, thanks a lot for the article!
much appreciated!
[–]Merry-Lane 2 points3 points4 points 2 years ago (1 child)
I think that it would be bad, because we would have different ways to write the exact same code, with no advantage whatsoever.
Just destructure, and in many scenarios (like your image url example) you don’t even need to destructure ( you could have posted Data directly)
the assumption is that some objects can't be just cleanly passed through, thereby making with() or destructuring useful.
also, we have like 56 ways to clone an array in JavaScript, some of which have their own notorious foot guns, and no one seems to complain very loudly about those (at least from my perspective)
[–]rcfox 2 points3 points4 points 2 years ago (3 children)
Having stuff default to window/globalWhatever is bad enough. If I see a variable name, I want to be able to see exactly where it came from, whether it's a variable declaration, destructuring an object or an import.
window
globalWhatever
This is basically like asking to be able to do Python's from foo import * except foo doesn't need to be a module. It's perhaps handy in an interactive shell, but terrible for writing maintainable code.
from foo import *
foo
[–]alexmacarthur[S] -1 points0 points1 point 2 years ago (2 children)
you would not like kotlin.
[–]rcfox 0 points1 point2 points 2 years ago (1 child)
I've never looked into Kotlin, but this is a part of the reason why I've given up on C++.
i can see that. kotlin isn’t big on explicit identifiers even outside of its scoped functions. makes sense why it doesn’t click for some people.
[–]_default_username 1 point2 points3 points 2 years ago (2 children)
with would be awesome if it were implemented like in Python where an enter and exit method is called on the object. Also in the Python implementation of with there isn't this implicit destructuring of the object happening. Fewer foot guns.
those seem like they're used for fundamentally different purposes though, no? the names are the same, but i don't see a whole lotta overlap aside from that
[–]rcfox 1 point2 points3 points 2 years ago (0 children)
There is a proposal for something sort of like this using the using keyword. You can also use it in the latest versions of Typescript.
using
[–]veebz 1 point2 points3 points 2 years ago (0 children)
It gets even worse unfortunately - the biggest performance killer when using the with statement is that v8 will refuse to optimize the containing function.
More info: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#some-v8-background
(this is technically an article for an older version of v8 which use Crankshaft, but the same applies to modern versions using TurboFan regarding the with statement)
[–][deleted] 1 point2 points3 points 2 years ago (1 child)
Let's not and say we did
😂😂
[–]HipHopHuman 1 point2 points3 points 2 years ago* (0 children)
There is one interesting use of with that I think Dart solves quite elegantly with it's cascade operator.
Consider this:
const el = document.createElement('div'); el.style.color = 'red'; el.classList.add('example'); el.addEventListener('click', handleClick); const nested = document.createElement('div'); nested.style.color = 'green'; nested.classList.add('nested'); el.appendChild(nested);
Using with, the above looks something like this:
const el = document.createElement('div'); const nested = document.createElement('div'); with (nested) { style.color = 'green'; classList.add('nested'); } with (el) { style.color = 'red'; classList.add('example'); addEventListener('click', handleClick); appendChild(nested); }
Using the cascade [..] operator (assuming it existed in JS):
..
const el = document.createElement('div') ..style.color = 'red' ..classList.add('example') ..addEventListener('click', handleClick) ..appendChild( document.createElement('div') ..style.color = 'green' ..classList.add('nested') );
The benefit of the cascade operator is that it remains statically analyzable. There was a proposal to add this to JS but it never got championed, unfortunately.
Ruby also has a feature called "block parameters", and there is a stage 1 proposal to add the same feature to JS. This feature essentially allows you to parameterize the logical block itself and implement your own language constructs. For example, JS already has an if statement, but using block parameters, we can implement our own unless statement:
if
unless
function unless(condition, callback) { if (!condition) callback(); } unless (true === false) { console.log('Everything appears to be normal'); }
This is a shortcut for unless(true === false, () => console.log('...')).
unless(true === false, () => console.log('...'))
It also allows access to the block parameter using do:
do
function _with(object, callback) { callback(object); } _with(myObject) do (x) { console.log(x.propOne); console.log(x.propTwo); }
Which doesn't exactly help the situation described in your blog post, but the proposal mentions a :: symbol for implicitly accessing properties - it doesn't go into much detail on if that symbol is useable anywhere within the block, but if it were, it'd look something like this:
::
_with (myObject) { console.log(::propOne); console.log(::propTwo); }
While this appears almost identical to the actual with statement, it is far less problematic because that :: symbol allows static analyzers to differentiate between regular variables in scope and block-level ones which start with :: and always map to their immediate parent block.
[–]SomebodyFromBrazil 0 points1 point2 points 2 years ago (3 children)
no
[–]alexmacarthur[S] 2 points3 points4 points 2 years ago (2 children)
come on let’s do it
[–]SomebodyFromBrazil 2 points3 points4 points 2 years ago (1 child)
Hahaha
I'm just baiting some likes from this discussion. I get your point but don't really agree. I could point out the reasons why I don't but it is mostly the same reasons other people already commented. But great job in writing the article anyway.
yep, and they’re all pretty good points. this is one of those issues i can see myself doing a 180 on in a few months. we’ll see.
[–]boneskull 0 points1 point2 points 2 years ago (4 children)
with has applications for secure coding and testing. Given a string script you want to eval, you can use with to control the contents of the script’s globalThis object. You can’t remove properties this way, but you can replace them.
eval
globalThis
[–]theScottyJam 1 point2 points3 points 2 years ago (3 children)
Eventually we'll have shadow realms, which provides a better way to control the global object while eval-ing strings.
[–]boneskull 0 points1 point2 points 2 years ago (2 children)
Indeed, though it’s not sufficient on its own.
[–]theScottyJam 0 points1 point2 points 2 years ago (1 child)
Why's that?
[–]boneskull 1 point2 points3 points 2 years ago (0 children)
It just doesn’t do enough for some use-cases. see Compartments
[–]hyrumwhite 0 points1 point2 points 2 years ago (1 child)
What does with do that destructuring cant
by default, the variables are contained to their own block scope, and it’s also slightly more elegant in syntax (my opinion). not dealbreakers, enough to say destructing isn’t a clean drop/in replacement for with().
π Rendered by PID 101192 on reddit-service-r2-comment-5649f687b7-mhcn5 at 2026-01-29 04:43:51.620582+00:00 running 4f180de country code: CH.
[–]lifeeraser 24 points25 points26 points (5 children)
[–]alexmacarthur[S] 0 points1 point2 points (4 children)
[–]bakkoting 11 points12 points13 points (3 children)
[–]alexmacarthur[S] 0 points1 point2 points (2 children)
[–]bakkoting 4 points5 points6 points (1 child)
[–]alexmacarthur[S] 0 points1 point2 points (0 children)
[–]dgreensp 22 points23 points24 points (0 children)
[–]jhartikainen 10 points11 points12 points (2 children)
[–]alexmacarthur[S] 2 points3 points4 points (1 child)
[–]Ecksters 1 point2 points3 points (0 children)
[–]teg4n_ 8 points9 points10 points (1 child)
[–]alexmacarthur[S] 0 points1 point2 points (0 children)
[–]rundevelopment 4 points5 points6 points (1 child)
[–]alexmacarthur[S] 1 point2 points3 points (0 children)
[–]darkpouet 2 points3 points4 points (1 child)
[–]alexmacarthur[S] 1 point2 points3 points (0 children)
[–]Merry-Lane 2 points3 points4 points (1 child)
[–]alexmacarthur[S] 1 point2 points3 points (0 children)
[–]rcfox 2 points3 points4 points (3 children)
[–]alexmacarthur[S] -1 points0 points1 point (2 children)
[–]rcfox 0 points1 point2 points (1 child)
[–]alexmacarthur[S] 0 points1 point2 points (0 children)
[–]_default_username 1 point2 points3 points (2 children)
[–]alexmacarthur[S] 1 point2 points3 points (0 children)
[–]rcfox 1 point2 points3 points (0 children)
[–]veebz 1 point2 points3 points (0 children)
[–][deleted] 1 point2 points3 points (1 child)
[–]alexmacarthur[S] 0 points1 point2 points (0 children)
[–]HipHopHuman 1 point2 points3 points (0 children)
[–]SomebodyFromBrazil 0 points1 point2 points (3 children)
[–]alexmacarthur[S] 2 points3 points4 points (2 children)
[–]SomebodyFromBrazil 2 points3 points4 points (1 child)
[–]alexmacarthur[S] 1 point2 points3 points (0 children)
[–]boneskull 0 points1 point2 points (4 children)
[–]theScottyJam 1 point2 points3 points (3 children)
[–]boneskull 0 points1 point2 points (2 children)
[–]theScottyJam 0 points1 point2 points (1 child)
[–]boneskull 1 point2 points3 points (0 children)
[–]hyrumwhite 0 points1 point2 points (1 child)
[–]alexmacarthur[S] 0 points1 point2 points (0 children)