What survives
Published
Newer operators are the natural place to look for transform bugs, because the ES5 target cannot emit them and has to rewrite each one into something older. That rewrite is where behaviour usually gets lost. Measured operator by operator, this set comes through clean.
Why these are worth measuring separately
There are two target versions, and they do very different amounts of work. On the modern target an operator like ??= can be emitted as it was written. On the default ES5 target it cannot, so the engine has to express the same semantics using older syntax.
That rewrite is not mechanical. a ??= b assigns only when a is null or undefined, which is not the same as a || b, and it must not evaluate the right side at all when the assignment is skipped. Getting a short-circuit slightly wrong produces code that works on most inputs and misbehaves on falsy ones, which is the hardest class of bug to notice.
So each operator was executed rather than inspected: original and protected build both run in node, outputs diffed.
The results
a ??= "filled" against a null assigned the value. b ||= 5 against 0 assigned, because zero is falsy. c &&= 9 against 1 assigned, because one is truthy. Those three cases are chosen to be the ones that distinguish nullish from falsy, and all three matched the original on both targets.
Exponentiation matched: 2 ** 10 gave 1024, and the compound form e **= 2 gave 9 from 3.
Numeric separators matched in both bases: 1_000_000 came through as one million and 0b1010_1010 as 170. Separators are purely lexical, so the interesting question was whether the lexer drops or mangles them, and it does neither.
Optional catch binding matched: try { ... } catch { ... } with no parameter still caught and still ran its handler.
What the ES5 path actually has to do
It is worth being clear that “identical output” does not mean “identical code”. On the ES5 target these operators are gone from the emitted file; they have been rewritten. The behaviour is what was preserved, which is the only thing that matters at runtime and the only thing a test can check.
This also explains why the two targets are worth thinking about as a compatibility lever rather than a cosmetic setting. If you are shipping to environments that genuinely predate these operators, the ES5 target is doing real work for you. If your floor is a modern browser, the modern target asks the engine to rewrite less, and less rewriting means fewer places for behaviour to shift.
How to check your own operator surface
This article measured a specific set. Your codebase will contain a different one, and the honest position is that a clean result here is evidence about these operators rather than a general guarantee about every construct you use.
The check that generalises is the one that does not depend on knowing which operators matter: run your existing test suite against a protected build. Tests that assert values catch semantic drift in exactly the places where a rewrite could have gone wrong, and they do it without anyone having to enumerate the language first.
If a specific expression is worth isolating, the smallest useful test is a file that prints the result of that expression, protected and unprotected, with the two outputs compared. That is precisely the method used above, and it takes a few minutes.
Frequently asked questions
Do logical assignment operators survive obfuscation?
Yes. Measured by executing the protected output, ??=, ||= and &&= all produced the same results as the original on both the ES5 and the modern target, including the cases that distinguish nullish from merely falsy.
Does ??= still skip the assignment for a value of zero?
Yes. Zero is not nullish, so ??= leaves it alone while ||= replaces it. Both behaviours were preserved in the protected build, which is the specific distinction a careless rewrite would lose.
Are numeric separators preserved?
The values are. 1_000_000 and 0b1010_1010 evaluated to one million and 170 after protection. Separators are lexical, so they do not survive as characters in the emitted file, but the numbers they describe are unchanged.
Does optional catch binding still work?
Yes. A catch block written without a parameter still caught its exception and ran its handler after protection on both targets.
Does the ES5 target still emit these operators?
No, and it is not supposed to. It rewrites them into older syntax that behaves the same way. What was measured and preserved is the behaviour, not the characters.
Does a clean result here mean all modern syntax is safe?
No. It is evidence about the operators listed here, measured on this engine. The way to get the same confidence about your own code is to run your existing test suite against a protected build.
Related reading