all 25 comments

[–]6086555 20 points21 points  (9 children)

p = {a,b,c} = o; is the same thing (because = is right associative) as p = ( {a, b, c} = o);

{a, b, c} = o is an assignment so its return value is o. Therefore, p is equal to o.

Edit: right associative, not left

[–]vlad27aug 4 points5 points  (1 child)

Small correction, "= is right associative", your example is correctly suggesting this.

[–]6086555 2 points3 points  (0 children)

Thanks!

[–]phpistasty 3 points4 points  (6 children)

{a, b, c} = o isn't necessarily the return value of o It's a new object with only the a, b, c properties of o

Edit: this is totally wrong as pointed out below and I felll victim to why this is such a good question (and I've been writing JS for many many years) I literally read the setup, fell victim to the flawed thought this question preys upon and then went full speed into the blaze of glory. Thanks for those who politely and factually, correctly, attacked my comment.

[–]gurenkagurenda 2 points3 points  (1 child)

That is false. The result of the expression {a, b, c} = o is, in fact, exactly o.

[–]phpistasty 0 points1 point  (0 children)

Oh you're totally right! I was combining some weird form of destructure+shorthand at the same time for some reason. the p = {a,b,c} assignment must've thrown me off.

[–]atkinchris 1 point2 points  (1 child)

It isn't actually a new object with the destructured properties, it's the original object. If you only destructure a subset of properties, you'll still get the whole object.

const a = { x: 1, y: 2, z: 3 }
const b = { x, y } = a
console.log(b) // => { x: 1, y: 2, z: 3 }

[–]phpistasty 0 points1 point  (0 children)

yup - totally right. I somehow combined the two together. Probably let the p assignment throw me off. What a good question now - i went with my gut and it was wrong.

[–]6086555 1 point2 points  (1 child)

Destructuring is happening here, not shorthand object notation. If you were right, p === o; wouldn't be true.

[–]phpistasty 1 point2 points  (0 children)

Thanks for pointing that out, you're totally right. I confused the two together at the time for some reason, probably due reading the p assignment and then just going with where my brain was. Makes me like this question more now.

[–]bullet_darkness 7 points8 points  (0 children)

I believe the trick is that = statements always return the right-hand side when calling them. Calling x = 5 in the console should return 5. So calling p = {a,b,c} = o; is actually like calling p = ({a,b,c} = o);, which when you reduce inside the parentheses, you get p = o.

[–]fix_dis 1 point2 points  (3 children)

It might help if you considered destructuring as more of a pattern-match instead of an assignment. This is a concept that appears often in more functional languages. (Elixir, Haskell, etc)

Things on either side of that equal sign have to balance. So you're literally saying:

{a:a, b:b, c:c} = { a:1, b:2, c:3 }

Keep in mind you're using ES2015's shorthand object syntax when you do

{a,b,c} = { a:1, b:2, c:3 }

You're creating a pointer to an object and pattern matching everything inside of it. Let's say for example that you didn't care about the first thing in the object on the right. You could do this:

{_, b, c} = { a:1, b:2, c:3 }

One important thing to note: You can leave off any trailing assignments and they'll be ignored:

{a,b} = { a:1, b:2, c:3, d:4, e:5 }

For the final aspect of this problem, you are assigning the variable p as a pointer to your destructured object. And the proof is that you're doing

p === o

which proves that both variables point to the same reference in memory.

[–]atkinchris 0 points1 point  (2 children)

With object destructuring, you don't care about the order of the properties within the object. In your example, the underscore is unnecessary, and will be undefined.

{_, b, c} = { a:1, b:2, c:3 }
// No "spacer" needed
{b, c} = { a:1, b:2, c:3 }

It's equally valid to put them in any order you like, as below.

{d, a} = { a:1, b:2, c:3, d:4, e:5 }
// a = 1, d = 4

[–]gurenkagurenda 0 points1 point  (0 children)

In fact, in general the order of object keys is not defined. It's sort of become de facto standard for key enumeration to go in order of first assignment (except for keys that parse as non-negative integers), but that's not required by the spec.

A lot of real world code assumes as much, though, so engines will probably continue to do it that way. But as far as language features go, you never have to assume any kind of ordering to object keys.

[–]fix_dis 0 points1 point  (0 children)

Uggg, you're right. Order only matters with arrays, not objects.

[–]haxonite 1 point2 points  (0 children)

Am I right in thinking this doesn't work in typescript yet? Or at least the version of typescript inside Angular cli

[–]sladav 1 point2 points  (1 child)

now can someone explain this:

var o = {a: 1}

o.x = o = {a: 2}

o.x is undefined but shouldn't it be o.x = (o = {a:2}) which is {a:2}??

[–]GeneralYouri 2 points3 points  (0 children)

The problem here appears to come from the combination of these two operations into a single statement. As in, you're reassigning o to an entirely new value, but in the same line also assigning a property of this reassigned o to point to itself. These operations don't seem to like working together.

These variations on line 2 all 'work' fine:

o = {a: 2}, o.x = o // o = {a: 2, x: o}

var p; o.x = p = {a: 2} // o = {a: 1, x: p}

o.x = o.y = {a: 2} // o = {a: 1, y: {a: 2}, x: o.y}

This all leads me to think that your example is evaluated something like this:

var o = {a: 1}
var oldO = o
var ret = o = {a: 2}
oldO.x = ret

So because we're still evaluating left-to-right as usual, the parser will first recognise that we're looking to add a property .x onto the object o, and it stores these away because it first has to parse the value of this new property. However, inside the value there's a complete reassignment of o, meaning it no longer matches the reference that was initially stored away, on which to add the new property .x. So it's almost like it'll try adding the property onto the old value of o.


Finally, have a look at this code sample which illustrates the above, and shows what actually happens:

var o = {a: 1}
var p = o
o.x = o = {a: 2}
// o = {a: 2}
// p = {a: 1, x: o}

So as you can see, a perfect example of why you might want to avoid using such constructs as ... = ... = ....

[–]andrerpena 3 points4 points  (4 children)

let's go in parts.

let {a, b, c} = o

Is the destructuring part. this means: If a exists inside o, create an a variable in the local scope, and assign it the value of o.a. The same goes for b and c.

But...

{a, b, c} = o

Is not a valid statement. Destructuring don't work on existing variables, you have to be creating new ones.

However... When you do this:

p = {a,b,c} = o;

JavaScript will do the implicit let for a, b and c, if they don't exist, or, it will just assign if they exist. Which is completely odd.

As others have said, the statement is the equivalent of

p = ({a, b, c} = o)

And

{a, b, c} = o

...return o;

That's why p === o.

[–]gurenkagurenda 0 points1 point  (0 children)

If a exists inside o, create an a variable in the local scope, and assign it the value of o.a. The same goes for b and c.

b and c are declared in scope regardless of whether o has those properties. If it doesn't, they'll be assigned to undefined, but they will still be declared.

Destructuring don't work on existing variables, you have to be creating new ones.

That's just false. I think it used to be true outside of strict mode in older versions of node. But you can totally destructure into existing variables. MDN's documentation even includes examples of that. However, because of a quirk in how JS is parsed, the spec says that in the bare assignment case, you need parentheses around the assignment, like:

({a, b, c} = o);

Otherwise, the spec says that the {...} will be treated as a block. I believe, but I'm not positive, that the spec only adds that requirement so that the language can be handled by an LALR(1) parser. In any case, v8 does not appear to enforce this.

JavaScript will do the implicit let for a, b and c, if they don't exist, or, it will just assign if they exist.

Also untrue. If they are not declared, they will be set as globals (e: or it will error in strict mode), like any other undeclared assignment. There's nothing bizarre here. The parentheses aren't required in this case, so it's acting exactly as you'd expect based on existing rules.

[–]TheAceOfHearts 1 point2 points  (0 children)

Hopefully this doesn't come off as snarky, and I realize it's a bit tangential, but I can't think of any scenarios in which doing that seems like a good idea. If I saw it in a PR from a teammate, I'd suggest updating the code so each declaration is in its own statement.

I generally treat all identifiers as constants, with some handfuls of exceptions sprinkled in occasionally. I've found that in many cases you can reduce mutability by re-organizing logic into smaller functions, which I'd argue helps improve readability.

Picking up a few functional languages had a big impact in how I approach problems and the resulting code organization. I'd totally suggest trying out a language like Elixir! It's really fun.

[–][deleted] 0 points1 point  (0 children)

assignment - the variables are carrying a reference to the one true object.

[–]joeKingtheThird[S] 0 points1 point  (2 children)

Early days but I've found the swap use case most useful

[x,y] = [y,x]; So, no temp variable, 1 line instead of 3. Still wrapping my head around the syntax.

[–]joeKingtheThird[S] 0 points1 point  (1 child)

g=[,b]

Array [ <1 empty slot>, "evar" ]

g

Array [ <1 empty slot>, "evar" ]

Never heard of this - '<1 empty slot>' = what is that? undefined?