3 ms·
The problem with this is it tremendously simplifies the issues of with(), and this simplification makes the semantic and performance impacts of with() seem much
by olliej 3y ago
The problem with this is it tremendously simplifies the issues of with(), and this simplification makes the semantic and performance impacts of with() seem much less bad that they actually are.
For semantic impact: it's not just walking the prototype chain, it's that the look up is 100% dynamic which means the lookups can change midway through execution, e.g.
let thing = ....;
let count = ....;
with (o) {
for (let j = 0; j < count; j++) {
thing();
}
}
Now lets imagine `thing()` adds a property named `thing` or `count` to anything in `o` or its prototype chain then the lookup of count or thing changes. It is _not_ a bunch of additions to the scope chain at the entry to the `with` block, it makes every identifier lookup that isn't lexically scoped inside the `with` (e.g. only `let` lookups inside their defining local scope). Will repeatedly attempt o.<identifier> every single time, and adding or removing those properties to `o` directly or indirectly during execution will change how even local variables are shadowed while inside the `with`, even in "static" loops.
This brings about the performance overhead: without a with block looking up a local variable (var, let, arguments) is generic a single load, even looking up a global variable is generally just a single additional indirect load. Once a single `with` is involved the best case is that every local variable access requires multiple indirect loads and a comparison to validate that the normal load path is involved. That's the ideal case where the property cache hits 100% of the time.
The semantic issues make with() a foot gun, essentially it means that adding new APIs that touch the standard Object prototype chain become even harder, it makes code more fragile in the face of other code changes, and the fragility is subtle and can't be statically detected and isn't a 100% failure case. That's why it was deprecated from JS. Other languages that support with() or equivalent (e.g. pascal family) have static typing so the identifier resolution is (1) static so doesn't impact performance and (2) can warn about shadowing.
From an engine point of view there's an immense amount of complexity of trying to get remotely decent performance of code inside `with` blocks (and it would still be an order of magnitude slower than non-with code), that would be to support a feature that wasn't heavily used even before the modern era of JS, and has negligible use now.