1

Just came from answering a question, and was left a bit stumped as to why the following seems to work in JavaScript:

const source = {
  last: "Capulet"
};

const srcKeys = Object.keys(source);

console.log(source[srcKeys]);
console.log(source[[[[srcKeys]]]]);
console.log(source[[],[[[srcKeys]]]]);
console.log(source[[],[[srcKeys]]]);
console.log(source[[[]],[],srcKeys]);
console.log(source[[[]],[[srcKeys]]]);

In an attempt to see how deep the rabbit hole goes, I found that the following do not work:

const source = {
  last: "Capulet"
};

const srcKeys = Object.keys(source);

console.log(source[[[],[[srcKeys]]]]);
console.log(source[[[],[],[[srcKeys]]]]);
console.log(source[[[[]],[[srcKeys]]]]);

What's the logic here?

smac89
  • 39,374
  • 15
  • 132
  • 179

1 Answers1

1

After doing some research on this problem, it turns out there is actually a method to the madness.

Comma operator

This one wasn't immediately obvious from my testing, but it is quite useful to understand wrt the examples that worked.

console.log(source[srcKeys]);
console.log(source[[[[srcKeys]]]]);
console.log(source[[],[[[srcKeys]]]]);
console.log(source[[],[[srcKeys]]]);
console.log(source[[[]],[],srcKeys]);
console.log(source[[[]],[[srcKeys]]]);

You will notice there is actually a pattern that was not initially obvious with these statements.

Each property accessor is either a comma expression (which will evaluate to the last value in the expression - usually an array) or just an array.

console.log(source[srcKeys]); // => array
console.log(source[[[[srcKeys]]]]); // => array
console.log(source[[],[[[srcKeys]]]]); // => comma expression
console.log(source[[],[[srcKeys]]]); // => comma expression
console.log(source[[[]],[],srcKeys]); // => comma expression
console.log(source[[[]],[[srcKeys]]]); // => comma expression

What's more, each array is either a single element array or a nested array of single element arrays.

Armed with this knowledge, it is now a good time to try to understand how the ECMAScript spec lays out the evaluation of property accessors.

Property Accessor

I'll spare you the gory details and instead give a summary of what occurs nearly 99% of the time:

The property expression is first converted to string (unless it is a Symbol since es6), then a reference to the value which has that given property name is returned


So now we understand that JavaScript will simply attempt to convert any property accessor to a string, and use that string as the property name.

In addition to that, arrays have an interesting behaviour when converted to string. You can read this other answer to have an indepth understanding on how an array is transformed to a property name.

The not soo secret reveal

Let's now apply everything we know to the examples given and see why some worked and some didn't.

First, let's look at the examples that didn't work:

console.log(source[[[],[[srcKeys]]]]); // => property accessor expression is [[],[[srcKeys]]]
console.log(source[[[],[],[[srcKeys]]]]); // => property accessor expression is [[],[],[[srcKeys]]]
console.log(source[[[[]],[[srcKeys]]]]); // => property accessor expression is [[[]],[[srcKeys]]]

One consistent detail of each of those expressions is that they are all arrays of multiple elements. It quickly becomes obvious why these didn't give us the expected value, but the previous ones did. For that, I will now show the result of toString on all the arrays.

const source = {
  last: "Capulet"
};

const srcKeys = Object.keys(source);

// console.log(source[srcKeys]);
console.log(srcKeys.toString());

// console.log(source[[[[srcKeys]]]]);
console.log([
  [
    [srcKeys]
  ]
].toString());

// console.log(source[[],[[[srcKeys]]]]);
console.log(([], [
  [
    [srcKeys]
  ]
]).toString());

// console.log(source[[],[[srcKeys]]]);
console.log(([], [
  [srcKeys]
]).toString());

// console.log(source[[[]],[],srcKeys]);
console.log(([
  []
], [], srcKeys).toString());

// console.log(source[[[]],[[srcKeys]]]);
console.log(([
  []
], [
  [srcKeys]
]).toString());

// console.log(source[[[],[[srcKeys]]]]);
console.log([
  [],
  [
    [srcKeys]
  ]
].toString());

// console.log(source[[[],[],[[srcKeys]]]]);
console.log([
  [],
  [],
  [
    [srcKeys]
  ]
].toString());

// console.log(source[[[[]],[[srcKeys]]]]);
console.log([
  [
    []
  ],
  [
    [srcKeys]
  ]
].toString());

And there we have it. First examples that worked, all have a toString value of last, while the rest didn't.

Dharman
  • 30,962
  • 25
  • 85
  • 135
smac89
  • 39,374
  • 15
  • 132
  • 179
  • At first, I though flattening of the nested arrays is an interesting behavior. I expected that stringification of the array: `[[],[[srcKeys]]]` will output: `[,[srcKeys]]` BUT then i realized that array is stringified by calling `toString` on each array element. – Yousaf Aug 09 '21 at 05:45
  • @Yousaf that's right. Converting arrays to string in JS produces the same result as [`array.join(',')`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join) – smac89 Aug 09 '21 at 05:53