-
Notifications
You must be signed in to change notification settings - Fork 73
Updating spec text #73
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ Current Stage: | |
## Authors | ||
* Claude Pache (@claudepache) | ||
* Gabriel Isenberg (@the_gisenberg) | ||
* Dustin Savery (@dustinsavery) | ||
|
||
## Overview and motivation | ||
When looking for a property value that's deep in a tree-like structure, one often has to check whether intermediate nodes exist: | ||
|
@@ -29,19 +30,6 @@ var street = user.address?.street | |
var fooValue = myForm.querySelector('input[name=foo]')?.value | ||
``` | ||
|
||
The call variant of Optional Chaining is useful for dealing with interfaces that have optional methods: | ||
|
||
```js | ||
iterator.return?.() // manually close an iterator | ||
``` | ||
or with methods not universally implemented: | ||
```js | ||
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers | ||
// form validation fails | ||
return; | ||
} | ||
``` | ||
|
||
## Prior Art | ||
The following languages implement the operator with the same general semantics as this proposal (i.e., 1) guarding against a null base value, and 2) short-circuiting application to the whole chain): | ||
* C#: [Null-conditional operator](https://msdn.microsoft.com/en-us/library/dn986595.aspx) — null-conditional member access or index, in read access. | ||
|
@@ -58,7 +46,6 @@ The Optional Chaining operator is spelled `?.`. It may appear in three positions | |
```javascript | ||
obj?.prop // optional static property access | ||
obj?.[expr] // optional dynamic property access | ||
func?.(...args) // optional function or method call | ||
``` | ||
|
||
### Notes | ||
|
@@ -67,23 +54,19 @@ func?.(...args) // optional function or method call | |
## Semantics | ||
|
||
### Base case | ||
If the operand at the left-hand side of the `?.` operator evaluates to undefined or null, the expression evaluates to undefined. Otherwise the targeted property access, method or function call is triggered normally. | ||
If the operand at the left-hand side of the `?.` operator evaluates to undefined or null, the expression returns undefined or null respectively. Otherwise the targeted property is accessed normally. | ||
|
||
Here are basic examples, each one followed by its desugaring. (The desugaring is not exact in the sense that the LHS should be evaluated only once.) | ||
```js | ||
a?.b // undefined if `a` is null/undefined, `a.b` otherwise. | ||
a == null ? undefined : a.b | ||
|
||
a?.[x] // undefined if `a` is null/undefined, `a[x]` otherwise. | ||
a == null ? undefined : a[x] | ||
a?.b // if `a` is null/undefined, return null/undefined respectively, `a.b` otherwise. | ||
a == null ? a : a.b | ||
|
||
a?.b() // undefined if `a` is null/undefined | ||
a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function | ||
// otherwise, evaluates to `a.b()` | ||
a?.[x] // if `a` is null/undefined, return null/undefined respectively, `a[x]` otherwise. | ||
a === null ? a : a[x] | ||
|
||
a?.() // undefined if `a` is null/undefined | ||
a == null ? undefined : a() // throws a TypeError if `a` is neither null/undefined, nor a function | ||
// invokes the function `a` otherwise | ||
a?.b() // if `a` is null/undefined, return null/undefined respectively, | ||
a === null ? a : a.b() // throws a TypeError if `a.b` is not a function | ||
// otherwise, evaluates to `a.b()` | ||
``` | ||
|
||
### Short-circuiting | ||
|
@@ -92,17 +75,17 @@ If the expression on the LHS of `?.` evaluates to null/undefined, the RHS is not | |
|
||
```js | ||
a?.[++x] // `x` is incremented if and only if `a` is not null/undefined | ||
a == null ? undefined : a[++x] | ||
a == null ? a : a[++x] | ||
``` | ||
|
||
### Long short-circuiting | ||
|
||
In fact, short-circuiting, when triggered, skips not only the current property access, method or function call, but also the whole chain of property accesses, method or function calls directly following the Optional Chaining operator. | ||
In fact, short-circuiting, when triggered, skips not only the current property access, but also the whole chain of property accesses directly following the Optional Chaining operator. | ||
|
||
```js | ||
a?.b.c(++x).d // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented. | ||
a?.b.c(++x).d // if `a` is null/undefined, evaluates to null/undefined respectively. Variable `x` is not incremented. | ||
// otherwise, evaluates to `a.b.c(++x).d`. | ||
a == null ? undefined : a.b.c(++x).d | ||
a == null ? a : a.b.c(++x).d | ||
``` | ||
|
||
Note that the check for nullity is made on `a` only. If, for example, `a` is not null, but `a.b` is null, a TypeError will be thrown when attempting to access the property `"c"` of `a.b`. | ||
|
@@ -116,8 +99,8 @@ Let’s call *Optional Chain* an Optional Chaining operator followed by a chain | |
An Optional Chain may be followed by another Optional Chain. | ||
|
||
```js | ||
a?.b[3].c?.(x).d | ||
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d | ||
a?.b[3].c?.d(x) | ||
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c.d(x) | ||
// (as always, except that `a` and `a.b[3].c` are evaluated only once) | ||
``` | ||
|
||
|
@@ -134,37 +117,22 @@ That follows from the design choice of specifying the scope of short-circuiting | |
|
||
Note that, whatever the semantics are, there is no practical reason to use parentheses in that position anyway. | ||
|
||
### Optional deletion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to remove ”optional deletion”, you must add additional static semantics in Section The delete Operator, in order to forbid specifically a LeftHandSideExpression containing an OptionalChain to be used with the delete operator. As I said in #40, ”Optional deletion has very few practical use cases; but forbidding it has probably no practical utility either.” However, forbidding optional deletion adds positive complexity to the spec. |
||
|
||
Because the `delete` operator is very liberal in what it accepts, we have that feature for free: | ||
```js | ||
delete a?.b | ||
// delete (a == null ? undefined : a.b) // that *would* work if `? :` could return a Reference... | ||
a == null ? undefined : delete a.b // this is what we get, really | ||
``` | ||
|
||
## Not supported | ||
|
||
Although they could be included for completeness, the following are not supported due to lack of real-world use cases or other compelling reasons; see [Issue # 22](https://github.com/tc39/proposal-optional-chaining/issues/22) and [Issue #54](https://github.com/tc39/proposal-optional-chaining/issues/54) for discussion: | ||
Although they could be included for completeness, the following are not supported due to complexity, lack of real-world use cases, or other compelling reasons: | ||
|
||
* optional function execution: `a?.()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏#59 |
||
* optional construction: `new a?.()` | ||
* optional template literal: ``a?.`{b}` `` | ||
* constructor or template literals in/after an Optional Chain: `new a?.b()`, ``a?.b`{c}` `` | ||
|
||
The following is not supported, although it has some use cases; see [Issue #18](//github.com/tc39/proposal-optional-chaining/issues/18) for discussion: | ||
|
||
* optional property assignment: `a?.b = c` | ||
* optional assignment: `a?.b?.c = x` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, ”optional property assignment” was more precise than ”optional assignment”. We’re not discussing about an optional version of |
||
|
||
All the above cases will be forbidden by the grammar or by static semantics so that support might be added later. | ||
|
||
## FAQ | ||
|
||
[TODO: to be completed. In particular, discuss specific criticisms around long short-circuiting.] | ||
|
||
|
||
<dl> | ||
|
||
|
||
<dt>obj?.[expr] and func?.(arg) look ugly. Why not use obj?[expr] and func?(arg) as does <language X>? | ||
|
||
<dd> | ||
|
@@ -174,22 +142,10 @@ We don’t use the `obj?[expr]` and `func?(arg)` syntax, because of the difficul | |
Alternative syntaxes for those two cases each have their own flaws; and deciding which one looks the least bad is mostly a question of personal taste. Here is how we made our choice: | ||
|
||
* pick the best syntax for the `obj?.prop` case, which is expected to occur most often; | ||
* extend the use of the recognisable `?.` sequence of characters to other cases: `obj?.[expr]`, `func?.(arg)`. | ||
* extend the use of the recognisable `?.` sequence of characters to other cases: `obj?.[expr]`. | ||
|
||
As for <language X>, it has different syntactical constraints than JavaScript because of <some construct not supported by X or working differently in X>. | ||
|
||
|
||
|
||
<dt>Why does (null)?.b evaluate to undefined rather than null? | ||
|
||
<dd> | ||
|
||
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. | ||
|
||
|
||
|
||
<dt>Why do you want long short-circuiting?</dt> | ||
|
||
<dd> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above desugaring is incorrect.