If I guessed, I'd say the reason is because the meaning causes problems.
First, the optional chaining equivalent of
const x = foo.point?.x;
const y = foo.point?.y;
const z = foo.point?.z;
would more than likely look like
const { x, y, z } = foo.point?;
Neither this syntax, nor the one offered before
const { ?.x, ?.y, ?.z } = foo.point;
can work imo. The one I offered is not the optional chaining operator. Even if it were treated as such, the meaning would be
const { x, y, z } = foo.point || null; //Might be undefined. Don't recall.
which doesn't work, while the other would be problematic because the first term is a binary operator. That would break destructuring which looks for a valid identifier name as the first term after the braces.
That's not to say it can't be made to work, but it does toy around with the mental model a bit much.
According to what I know, compilers think that this is a ternary expression and complain. That's why optional method calling looks like foo.bar?.() and not foo.bar?().
The thing to the left of the destructuring operator ('?.') must be an object for the same reasons as the '.' operator. Since the '.' is removed when using destructuring, it made sense to me that the same should happen if optionally destructuring.
However, this shows the problem fairly clearly. With normal optional chaining, the engine is either returning the desired property or an "empty" value. Distributing optional chaining over destructuring would mean this:
const { x, y, z } = null
is the same as :
const x = null;
const y = null;
const z = null;
which is distinctly not true and would require a modification of how destructuring works.
Perhaps another angle to think of the issue, what does this do?
const point = undefined
const { ?.x, y } = point
It'll throw, because a property y does not exist on undefined. There's no value in applying this "?." syntax to only some of those properties, it's an all or nothing deal. Perhaps that's why @rdking is saying it might be more logical to put the syntax in a location where it can be an all or nothing deal.
const point = undefined
const { x, y } = point?.
Then, in his version, he was merely omitting the "." from "?.", because the property access is already implied when destructuring. It's not like const { x, y } = point. is valid syntax. He was then mentioning some issues with trying to figure out what point? would even evaluate to make this all work`. Perhaps a better syntax to achieve this effect would be something like this:
const ?{ x, y: ?{ a, b } } = point
i.e. I'm prefixing the whole destructuring object syntax with a "?" if I'm ok with it being optional.
Though once again, this syntax is mostly useless unless you're dealing with nested destructuring that has null values, and you don't wish to break up the nested destructuring. I don't know if you have use cases that fall into this category or not, but I'm certainly ok with breaking up a destructuring over multiple lines if I'm expecting a null value.
That's a good portion of it. Since I learned assembly as my second programming language, and C++ as my third, I've always understood operators to be implicit functions. So
const { x, y } = point?
looks to me like
function dereference(object, param) {
let retval = object;
if (object && typeof(object) == "object")
retval = object[param];
return retval;
}
const x = dereference(point, "x");
const y = dereference(point, "y");
However, parallel to what you said, I can't make a sensible function out of putting the operator on the property. So in my head, that notation is an instant fail.
I could see that syntax being extremely convenient with optional options parameters, replacing the {foo, bar} = {} idiom with something that no longer requires an object prototype lookup when missing (making it much faster).
I do see that as an acceptable compromise, though - I don't see myself doing optional destructuring of inner arrays that often, and it's pretty easy to just write another line to continue the destructuring.
That doesn't solve the OP, you'd need to put obj ?? {} on the right-hand-side.
Plus it's 1. drowning the name of the variable "c" in ascii soup; and 2. moving the key "a" further away from the obj you're pulling it from.
This is so much clearer:
const c = obj.a?.b?.c;
// or
const { c } = obj.a?.b ?? {};
Even though there's still a lot of soup to swim through, at least it reads left-to-right.
If someone can come up with a clean way to get rid of the ?? {}, that'd be great.
I get that when you want to pull things from multiple sub-objects, this one line can't do that. But if you have several nested optional objects you want to pull things from, perhaps you should split that into multiple steps — destructure one optional object at a time — for the sake of readability.
Now that I'm thinking about it a bit better, something that works with arguments would be far more useful (and would make the majority of use cases IMHO - think: object-based named parameters), and of course arrays would be naturally included in that.
What about this as assignment patterns as an alternative?
?{a} = b → if (b != null) {a} = b; else a = undefined
?[a] = b → if (b != null) [a] = b; else a = undefined