-
Notifications
You must be signed in to change notification settings - Fork 75
Changes from all commits
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 |
---|---|---|
|
@@ -25,18 +25,18 @@ var fooValue = fooInput ? fooInput.value : undefined | |
The Optional Chaining Operator allows a developer to handle many of those cases without repeating themselves and/or assigning intermediate results in temporary variables: | ||
|
||
```javascript | ||
var street = user.address?.street | ||
var fooValue = myForm.querySelector('input[name=foo]')?.value | ||
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 | ||
iterator.return??() // manually close an iterator | ||
``` | ||
or with methods not universally implemented: | ||
```js | ||
if (myForm.checkValidity?.() === false) { // skip the test in older web browsers | ||
if (myForm.checkValidity??() === false) { // skip the test in older web browsers | ||
// form validation fails | ||
return; | ||
} | ||
|
@@ -54,44 +54,45 @@ The following languages have a similar feature. We haven’t checked whether the | |
|
||
## Syntax | ||
|
||
The Optional Chaining operator is spelled `?.`. It may appear in three positions: | ||
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 | ||
obj??.prop // optional static property access | ||
obj??[expr] // optional dynamic property access | ||
func??(...args) // optional function or method call | ||
``` | ||
|
||
### Notes | ||
* In order to allow `foo?.3:0` to be parsed as `foo ? .3 : 0` (as required for backward compatibility), a simple lookahead is added at the level of the lexical grammar, so that the sequence of characters `?.` is not interpreted as a single token in that situation (the `?.` token must not be immediately followed by a decimal digit). | ||
### Why two question marks instead of one? | ||
|
||
An earlier version of this proposal used `??.` for the optional chaining operator, with `??[` for dynamic property access and `??(` for optional function/method calls. The switch to two question marks allows for more consistency and the ability to omit the surprising `.` in the latter two cases. See [the past discussion](https://github.com/tc39/proposal-optional-chaining/issues/34) on this topic for more details. | ||
|
||
## 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 evaluates to undefined. Otherwise the targeted property access, method or function call is triggered 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??.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??[x] // undefined if `a` is null/undefined, `a[x]` otherwise. | ||
a == null ? undefined : a[x] | ||
|
||
a?.b() // undefined if `a` is null/undefined | ||
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?.() // undefined if `a` is null/undefined | ||
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 | ||
``` | ||
|
||
### Short-circuiting | ||
|
||
If the expression on the LHS of `?.` evaluates to null/undefined, the RHS is not evaluated. This concept is called *short-circuiting*. | ||
If the expression on the LHS of `??.` evaluates to null/undefined, the RHS is not evaluated. This concept is called *short-circuiting*. | ||
|
||
```js | ||
a?.[++x] // `x` is incremented if and only if `a` is not null/undefined | ||
a??[++x] // `x` is incremented if and only if `a` is not null/undefined | ||
a == null ? undefined : a[++x] | ||
``` | ||
|
||
|
@@ -100,7 +101,7 @@ a == null ? undefined : a[++x] | |
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. | ||
|
||
```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 undefined. Variable `x` is not incremented. | ||
// otherwise, evaluates to `a.b.c(++x).d`. | ||
a == null ? undefined : a.b.c(++x).d | ||
``` | ||
|
@@ -116,7 +117,7 @@ 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??.b[3].c??(x).d | ||
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d | ||
// (as always, except that `a` and `a.b[3].c` are evaluated only once) | ||
``` | ||
|
@@ -126,7 +127,7 @@ a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d | |
Parentheses limit the scope of short-circuting: | ||
|
||
```js | ||
(a?.b).c | ||
(a??.b).c | ||
(a == null ? undefined : a.b).c | ||
``` | ||
|
||
|
@@ -138,7 +139,7 @@ Note that, whatever the semantics are, there is no practical reason to use paren | |
|
||
Because the `delete` operator is very liberal in what it accepts, we have that feature for free: | ||
```js | ||
delete a?.b | ||
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 | ||
``` | ||
|
@@ -147,13 +148,13 @@ a == null ? undefined : delete a.b // this is what we get, really | |
|
||
The following are not supported due to a lack of real-world use cases: | ||
|
||
* optional construction: `new a?.()` | ||
* optional template literal: ``a?.`{b}` `` | ||
* constructor or template literals in/after an Optional Chain: `new a?.b()`, ``a?.b`{c}` `` | ||
* optional construction: `new a??()` | ||
* optional template literal: ``a??`{b}` `` | ||
* constructor or template literals in/after an Optional Chain: `new a??b()`, ``a??.b`{c}` `` | ||
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.
|
||
|
||
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 property assignment: `a??.b = c` | ||
|
||
All the above cases will be forbidden by the grammar or by static semantics so that support might be added later. | ||
|
||
|
@@ -165,28 +166,13 @@ All the above cases will be forbidden by the grammar or by static semantics so t | |
<dl> | ||
|
||
|
||
<dt>obj?.[expr] and func?.(arg) look ugly. Why not use obj?[expr] and func?(arg) as does <language 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. Instead of deleting that FAQ item, let’s replace it with:
Further adaptations below. |
||
|
||
<dd> | ||
|
||
We don’t use the `obj?[expr]` and `func?(arg)` syntax, because of the difficulty for the parser to efficiently distinguish those forms from the conditional operator, e.g., `obj?[expr].filter(fun):0` and `func?(x - 2) + 3 :1`. | ||
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. Let’s be more affirmative: ”We can’t use...” |
||
|
||
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: | ||
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. As a temporary measure, replace with: ”[TODO: explain the rationale and the tradeoffs behind the choice of |
||
|
||
* 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)`. | ||
|
||
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? | ||
<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`. | ||
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. | ||
In particular, the value `null` is considered to have no properties; therefore, `(null)??.b` is undefined. | ||
|
||
|
||
|
||
|
@@ -198,19 +184,19 @@ See [Issue #3 (comment)](https://github.com/tc39/proposal-optional-chaining/issu | |
|
||
|
||
|
||
<dt>In a?.b.c, if a.b is null, then a.b.c will evaluate to undefined, right? | ||
<dt>In a??.b.c, if a.b is null, then a.b.c will evaluate to undefined, right? | ||
|
||
<dd> | ||
|
||
No. It will throw a TypeError when attempting to fetch the property `"c"` of `a.b`. | ||
|
||
The opportunity of short-circuiting happens only at one time, just after having evaluated the LHS of the Optional Chaining operator. If the result of that check is negative, evaluation proceeds normally. | ||
|
||
In other words, the `?.` operator has an effect only at the very moment it is evaluated. It does not change the semantics of subsequent property accesses, method or function calls. | ||
In other words, the `??.` operator has an effect only at the very moment it is evaluated. It does not change the semantics of subsequent property accesses, method or function calls. | ||
|
||
|
||
|
||
<dt>In a deeply nested chain like `a?.b?.c`, why should I write `?.` at each level? Should I not be able to write the operator only once for the whole chain?</dt> | ||
<dt>In a deeply nested chain like `a??.b??.c`, why should I write `??.` at each level? Should I not be able to write the operator only once for the whole chain?</dt> | ||
|
||
<dd> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,7 @@ | |
<h1>Scope</h1> | ||
<p>This is the spec text of the <a href="https://github.com/tc39/proposal-optional-chaining/">Optional Chaining proposal</a> in ECMAScript. </p> | ||
|
||
<p>For the syntax, we use the `?.` token, with a lookahead at the level of the lexical grammar that allows to discriminate between `a?.b` (optional chaining) and `a?.3:0` (conditional operator, whose meaning cannot be changed due to backward compatibility constraints).</p> | ||
<p>For the syntax, we use the `??.` token. An earlier version of this proposal used `?.`</p> | ||
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. In fact, we use three tokens, namely
|
||
|
||
<p>An <a href="https://claudepache.github.io/es-optional-chaining/">early version of this proposal</a> used a Nil reference to express short-circuiting. This one is based on syntax only.</p> | ||
|
||
|
@@ -50,11 +50,7 @@ <h1>Punctuators (<a href="https://tc39.github.io/ecma262/#sec-punctuators">11.7< | |
<h2>Syntax</h2> | ||
<emu-grammar> | ||
<ins class="block"> | ||
OptionalChainingPunctuator :: | ||
`?.` [lookahead <! DecimalDigit | ||
</ins> | ||
|
||
OtherPunctuator :: one of | ||
Punctuator :: | ||
`{` `(` `)` `[` `]` | ||
`.` `...` `;` `,` | ||
`<` `>` `<=` `>=` | ||
|
@@ -68,17 +64,7 @@ <h2>Syntax</h2> | |
`?` `:` | ||
`=` `+=` `-=` `*=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` | ||
`=>` | ||
|
||
Punctuator :: | ||
<ins>OptionalChainingPunctuator</ins> | ||
OtherPunctuator | ||
|
||
DivPunctuator :: | ||
`/` | ||
`/=` | ||
|
||
RightBracePunctuator :: | ||
`}` | ||
`??.` `??(` `??[` | ||
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. Should be surrounded by <ins>. |
||
</emu-grammar> | ||
</emu-clause> | ||
<!-- | ||
|
@@ -148,9 +134,9 @@ <h2>Syntax</h2> | |
OptionalExpression[?Yield, ?Await] OptionalChain[?Yield, ?Await] | ||
|
||
OptionalChain[Yield, Await] : | ||
OptionalChainingPunctuator `[` Expression[+In, ?Yield, ?Await] `]` | ||
OptionalChainingPunctuator IdentifierName | ||
OptionalChainingPunctuator Arguments[?Yield, ?Await] | ||
`??[` Expression[+In, ?Yield, ?Await] `]` | ||
`??.` IdentifierName | ||
`??(` ArgumentsList[?Yield, ?Await] `,`? `) | ||
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. You missed
|
||
OptionalChain[?Yield, ?Await] `[` Expression[+In, ?Yield, ?Await] `]` | ||
OptionalChain[?Yield, ?Await] `.` IdentifierName | ||
OptionalChain[?Yield, ?Await] Arguments[?Yield, ?Await] | ||
|
@@ -209,7 +195,7 @@ <h1>Static Semantics: Contains</h1> | |
1. Return *false*. | ||
</emu-alg> | ||
<ins class="block"> | ||
<emu-grammar>OptionalChain : OptionalChainingPunctuator IdentifierName</emu-grammar> | ||
<emu-grammar>OptionalChain : `??.` IdentifierName</emu-grammar> | ||
<emu-alg> | ||
1. If _symbol_ is a |ReservedWord|, return *false*. | ||
1. If _symbol_ is an |Identifier| and StringValue of _symbol_ is the same value as the StringValue of |IdentifierName|, return *true*. | ||
|
@@ -535,7 +521,7 @@ <h1>Tagged Templates (<a href="https://tc39.github.io/ecma262/#sec-tagged-templa | |
<ins class="block"> | ||
<emu-clause id="sec-optional-chains"> | ||
<h1>Optional Chains</h1> | ||
<emu-note>An optional chain is a chain of property accesses and function calls introduced by an |OptionalChainingPunctuator|.</emu-note> | ||
<emu-note>An optional chain is a chain of property accesses and function calls introduced by `??.`, `??(` or `??[`.</emu-note> | ||
</emu-note> | ||
<emu-clause id="sec-optional-chaining-evaluation"> | ||
<h1>Runtime Semantics: Evaluation</h1> | ||
|
@@ -558,23 +544,27 @@ <h1>Runtime Semantics: Evaluation</h1> | |
<emu-clause id="sec-optional-chaining-chain-evaluation"> | ||
<h1>Runtime Semantics: ChainEvaluation</h1> | ||
<p>With parameters _baseValue_ and _baseReference_.</p> | ||
<emu-grammar>OptionalChain : OptionalChainingPunctuator `[` Expression `]`</emu-grammar> | ||
<emu-grammar>OptionalChain : `??[` Expression `]`</emu-grammar> | ||
<emu-alg> | ||
1. If the code matched by this production is strict mode code, let _strict_ be *true*, else let _strict_ be *false*. | ||
1. Return ? EvaluateDynamicPropertyAccess(_baseValue_, |Expression|, _strict_). | ||
</emu-alg> | ||
<emu-grammar>OptionalChain : OptionalChainingPunctuator IdentifierName</emu-grammar> | ||
<emu-grammar>OptionalChain : `??.` IdentifierName</emu-grammar> | ||
<emu-alg> | ||
1. If the code matched by this production is strict mode code, let _strict_ be *true*, else let _strict_ be *false*. | ||
1. Return ? EvaluateStaticPropertyAccess(_baseValue_, |IdentifierName|, _strict_). | ||
</emu-alg> | ||
<emu-grammar>OptionalChain : OptionalChainingPunctuator Arguments</emu-grammar> | ||
<emu-grammar> | ||
OptionalChain : | ||
`??(` ArgumentsList? `)` | ||
</emu-grammar> | ||
<emu-grammar>OptionalChain : `??(` ArgumentsList `,` `)` </emu-grammar> | ||
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. I’d say (as above):
|
||
<emu-alg> | ||
1. Let _thisChain_ be this production. | ||
1. Let _tailCall_ be IsInTailPosition(_thisChain_). | ||
1. Return ? EvaluateCall(_baseValue_, _baseReference_, |Arguments|, _tailCall_). | ||
1. Return ? EvaluateCall(_baseValue_, _baseReference_, |ArgumentsList|, _tailCall_). | ||
</emu-alg> | ||
<emu-grammar>OptionalChain : OptionalChain `[` Expression `]`</emu-grammar> | ||
<emu-grammar>OptionalChain : `??[` Expression `]`</emu-grammar> | ||
<emu-alg> | ||
1. Let _optionalChain_ be this |OptionalChain|. | ||
1. Let _newReference_ be ? _optionalChain_.ChainEvaluation(_baseValue_, _baseReference_). | ||
|
@@ -642,8 +632,8 @@ <h1>Expression Rules</h1> | |
</emu-alg> | ||
<emu-grammar> | ||
OptionalChain : | ||
OptionalChainingPunctuator `[` Expression `]` | ||
OptionalChainingPunctuator IdentifierName | ||
`??[` Expression `]` | ||
`??.` IdentifierName | ||
OptionalChain `[` Expression `]` | ||
OptionalChain `.` IdentifierName | ||
</emu-grammar> | ||
|
@@ -652,7 +642,7 @@ <h1>Expression Rules</h1> | |
</emu-alg> | ||
<emu-grammar> | ||
OptionalChain : | ||
OptionalChainingPunctuator Arguments | ||
`??(` ArgumentsList `,`? `)` | ||
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. As above.
|
||
OptionalChain Arguments | ||
</emu-grammar> | ||
<emu-alg> | ||
|
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.
Too zealous search-and-replace? Replace back
??.
/??[
/??(
with?.
/?.[
/?.(
in that sentence.