Why does the TypeScript compiler compile its optional chaining and null-coalescing operators with two checks?
Asked Answered
G

1

62

Why does the TypeScript compiler compile its optional chaining and null-coalescing operators, ?. and ??, to

// x?.y
x === null || x === void 0 ? void 0 : x.y;

// x ?? y
x !== null && x !== void 0 ? x : y

instead of

// x?.y
x == null ? void 0 : x.y

// x ?? y
x != null ? x : y

?

Odds are that behind the scenes == null does the same two checks, but even for the sake of code length, it seems like single check would be cleaner. It adds many fewer parentheses when using a string of optional chaining, too.

Incidentally, I'm also surprised that optional chaining doesn't compile to

x == null ? x : x.y

to preserve null vs undefined. This has since been answered: Why does JavaScript's optional chaining to use undefined instead of preserving null?

Gintz answered 4/11, 2021 at 16:59 Comment(2)
Oh, I noticed "Incidentally, I'm also surprised that .." The answer is that optional chaining is not supposed to preserve null... it always produces undefined when the thing being referenced is nullish. The reason I'm putting this in a comment and not my answer below is that this really is a separate question and belongs in its own post.Bewilderment
Yeah, I threw that on at the end after you posted your answer. https://mcmap.net/q/323735/-why-does-javascript-39-s-optional-chaining-use-undefined-instead-of-preserving-null/3120446Gintz
B
79

You can find an authoritative answer in microsoft/TypeScript#16 (wow, an old one); it is specifically explained in this comment:

That's because of document.all [...], a quirk that gets special treatment in the language for backwards compatibility.

document.all == null // true
document.all === null || document.all === undefined // false

In the optional chaining proposal

document.all?.foo === document.all.foo

but document.all == null ? void 0 : document.all.foo would incorrectly return void 0.

So there is a particular idiosyncratic deprecated obsolete wacky legacy pseudo-property edge case of type HTMLAllCollection that nobody uses, which is loosely equal to null but not strictly equal to either undefined or null. Amazing!

It doesn't seem like anyone seriously considered just breaking things for document.all. And since the xxx === null || xxx === undefined version works for all situations, it's probably the tersest way of emitting backward-compatible JS code that behaves according to the spec.

Bewilderment answered 4/11, 2021 at 17:40 Comment(6)
Good ol' JavaScript. It's learning things like this that make me feel like JS is still as embarrassing as PHP. I wonder if we'll ever get a 'use strict 20XX' that does away with all the long-deprecated features.Gintz
"the main differences are that it allows a staggering variety of different (ab)uses of its methods" Whew, when you're saying that about JavaScript...Shiprigged
As an aside, in Babel there is a "noDocumentAll" assumption that you can enable to cause it to output the more terse version. I don't believe TS has an equivalent setting currently.Wiggins
I don't get why document.all === undefined is used instead of the safer typeof document.all === 'undefined'... Can someone please explain?Interosculate
@Interosculate - what makes you think it is in any way less safe nowadays? The says of undefined being writable are long gone.Nephron
You're right about that. My first thought was about using document.all === null || document.all === undefined, which gives false, instead of document.all === null || typeof document.all === 'undefined', which gives true, just like document.all == null. But I've surely missed a point.Interosculate

© 2022 - 2024 — McMap. All rights reserved.