3

This is a follow up question I asked incidentally at the end of Why does the TypeScript compiler compile its optional chaining and null-coalescing operators with two checks? As resident TypeScript legend jcalz pointed out in a comment, it does deserve its own question.

// Why does the JavaScript treat
x?.y
// as
x === null || x === void 0 ? void 0 : x.y
// instead of
x === null || x === void 0 ? x : x.y
// ?

When x == null, the latter would preserve null while the former always returns undefined.

Modern browsers support ?. natively, so we can test this behavior.

const test = () => {
  console.log('undefined?.x\t\t==>\t', undefined?.x);
  console.log('null?.x\t\t\t==>\t', null?.x);
  console.log('null?.x === null\t==>\t', null?.x === null);
};

try {
  eval('null?.x');
  test();
} catch {
  console.error('Your browser does not support optional chaining syntax.');
  console.info('While optional chaining is supported by all modern browsers, this will not work in browsers that do not support the syntax.')
  console.warn('');
  console.info('Shocking, I know.');
  console.info('Compatibility chart: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining#browser_compatibility');
}
Drenai
  • 11,315
  • 9
  • 48
  • 82
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
  • As I said in the other question, that's just how [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) works in JS; `null?.x` is `undefined` and not `null`. So are you asking why TS decided to implement downleveling so that it produces JS-spec compliant code? (Answer: it makes kittens sad when TS downlevels improperly ) Or are you asking why JS decided to make optional chaining short-circuit to `undefined` instead of preserving the particular flavor of nullishness passed in? (Answer: not sure yet) – jcalz Nov 05 '21 at 17:43
  • 2
    Ah, answer: https://github.com/tc39/proposal-optional-chaining#faq `null?.x` is `undefined` because the `x` property of `null` would be `undefined`, not `null` if you could dereference `null` like any other thing that lacks an `x` property. – jcalz Nov 05 '21 at 17:46
  • I had really been asking about JS I suppose, though I assumed JS just went with what TS decided to do, since TS had it first. Want to write that up as an answer? – dx_over_dt Nov 05 '21 at 18:44
  • 2
    Ah, there is a [process](https://tc39.es/process-document/) for features being introduced to JS. Once a proposal reaches "stage 3", it means it's very likely to appear in JS in the near future and the details of it are mostly stable. At that point, TS implements it according to the spec. So while it is technically true that "TS had it first" in the sense that a version of TS had the feature before it was officially part of an EcmaScript spec, it's not like they could just arbitrarily choose what it would do. – jcalz Nov 05 '21 at 18:49
  • 2
    So, I'm happy to answer, but it doesn't have much to do with TypeScript per se; maybe you should edit the question to be about JS instead? – jcalz Nov 05 '21 at 18:51
  • 1
    I updated it to be about JS – dx_over_dt Nov 05 '21 at 18:53

1 Answers1

8

In the FAQ for the optional chaining proposal, it says:

Why does (null)?.b evaluate to undefined rather than null?

  Neither a.b nor a?.b is intended to preserve arbitrary information on the base object a, but only to give information about the property "b" of that object. If a property "b" is absent from a, this is reflected by a.b === undefined and a?.b === undefined. In particular, the value null is considered to have no properties; therefore, (null)?.b is undefined.

So that seems to be the intended public-facing answer to this question. The property doesn't exist, and so optional chaining gives you the normal behavior for reading non-existent properties (undefined) without you needing to worry about throwing a TypeError.


Of course, you're not the only one to expect that a?.b should propagate nullishness of a instead. There is quite a lively debate about just what (null)?.b should be in the (now closed) issues tc39/proposal-optional-chaining#65 and tc39/proposal-optional-chaining#69.

In tc39/poc#65 we see that a reddit poll was conducted and the consensus favored "always undefined" over "sometimes null". And in tc39/poc#69 someone did a survey of JS libraries that had function versions of this sort of operator like Undercore's property and Lodash's get, and asked: "if someLibraryFunction({a: 123}, "a") produces 123, and someLibraryFunction(undefined, "a") produces undefined, what does someLibraryFunction(null, "a") produce?" It looks like the answer is undefined for most of the libraries consulted.

Neither of those are necessarily a definitive reason why we have undefined instead of null. But they indicate that the forces of undefined seem to have had some more power or stamina than the forces of null and that undefined ultimately prevailed, always going back to the argument that "a?.b is supposed to let you read the property b of a without worrying about TypeError".

jcalz
  • 264,269
  • 27
  • 359
  • 360