We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel πŸš€.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through JavaScript Works average a 15% increase in salary πŸš€

Blog hero image

Thinking JavaScript

Kyle Simpson 18 October, 2017 | 6 min read

I was teaching a JavaScript workshop the other day and one of the attendees asked me a JS brain teaser during the lunch break that really got me thinking. His claim was that he ran across it accidentally, but I'm a bit skeptical; it might just have been an intentional WTF trick!

Anyway, I got it wrong the first couple of times I was trying to analyze it, and I had to run the code through a parser then consult the spec (and a JS guru!) to figure out what was going on. Since I learned some things in the process, I figured I'd share it with you.

Not that I expect you'll ever intentionally write (or read, hopefully!) code like this, but being able to think more like JavaScript does always help you write better code.

The Setup

The question posed to me was this: why does this first line "work" (compiles/runs) but the second line gives an error?

[[]][0]++;

[]++;

The thinking behind this riddle is that [[]][0] should be the same as [], so either both should work or both should fail.

My first answer, after thinking about it for a few moments, was that these should both fail, but for different reasons. I was incorrect on several accounts. Indeed, the first one is valid (even if quite silly).

I was wrong despite the fact that I was trying to think like JavaScript does. Unfortunately, my thinking was messed up. No matter how much you know, you can still easily realize you don't know stuff.

That's exactly why I challenge people to admit: "You Don't Know JS"; none of us ever fully knows something as complex as a programming language like JS. We learn some parts, and then learn more, and keep on learning. It's a forever process, not a destination.

My Mistakes

First, I saw the two ++ operator usages, and my instinct was that these will both fail because the unary postfix ++, like in x++, is mostly equivalent to x = x + 1, which means the x (whatever it is) has to be valid as something that can show up on the left-hand side of an = assignment.

Actually, that last part is true, I was right about that, but for the wrong reasons.

What I wrongly thought was that x++ is sorta like x = x + 1; in that thinking, []++ being [] = [] + 1 would be invalid. While that definitely looks weird, it's actually quite OK. In ES6, the [] = .. part is valid array destructuring.

Thinking of x++ as x = x + 1 is misguided, lazy thinking, and I shouldn't be surprised that it led me astray.

Moreover, I was thinking of the first line all wrong, as well. What I thought was, the [[]] is making an array (the outer [ ]), and then the inner [] is trying to be a property access, which means it gets stringified (to ""), so it's like [""]. This is nonsense. I dunno why my brain was messed up here.

Of course, for the outer [ ] to be an array being accessed, it'd need to be like x[[]] where x is the thing being accessed, not just [[]] by itself. Anyway, thinking all wrong. Silly me.

Corrected Thinking

Let's start with the easiest correction to thinking. Why is []++ invalid?

To get the real answer, we should go to the official source of authority on such topics, the spec!

In spec-speak, the ++ in x++ is a type of "Update Expression" called the "Postfix Increment Operator". It requires the x part to be a valid "Left-Hand Side Expression" – in a loose sense, an expression that is valid on the left-hand side of an =. Actually, the more accurate way to think of it is not left-hand side of an =, but rather, valid target of an assignment.

Looking at the list of valid expressions that can be targets of an assignment, we see things like "Primary Expression" and "Member Expression", among others.

If you look into Primary Expression, you find that an "Array Literal" (like our []!) is valid, at least from a syntax perspective.

So, wait! [] can be a left-hand side expression, and is thus valid to show up next to a ++. Hmmm. Why then does []++ give an error?

What you might miss, which I did, is: it's not a SyntaxError at all! It's a runtime error called ReferenceError.

Occassionally, I have people ask me about another perplexing – and totally related! – result in JS, that this code is valid syntax (but still fails at runtime):

2 = 3;

Obviously, a number literal shouldn't be something we can assign to. That makes no sense.

But it's not invalid syntax. It's just invalid runtime logic.

So what part of the spec makes 2 = 3 fail? The same reason that 2 = 3 fails is the same reason that []++ fails.

Both of these operations use an abstract algorithm in the spec called "PutValue". Step 3 of this algorithm says:

If Type(V) is not Reference, throw a ReferenceError exception.

Reference is a special specification type that refers to any kind of expression that represents an area in memory where some value could be assigned. In other words, to be a valid target, you have to be a Reference.

Clearly, 2 and [] are not References, so that's why at runtime, you get a ReferenceError; they are not valid assignment targets.

But What About...?

Don't worry, I haven't forgotten the first line of the snippet, which works. Remember, my thinking was all wrong about it, so I've got some correcting to do.

[[]] by itself is not an array access at all. It's just an array value that happens to contain another array value as its only contents. Think of it like this:

var a = [];
var b = [a];

b;  // [[]]

See?

So now, [[]][0], what is that all about? Again, let's break it down with some temporary variables.

var a = [];
var b = [a];

var c = b[0];
c;  // [] -- aka, `a`!

So the original setup premise is correct. [[]][0] is kinda the same as just [] itself.

Back to that original question: why then does line 1 work but line 2 doesn't?

As we observed earlier, the "Update Expression" requires a "LeftHandSideExpression". One of the valid types of those expressions is "Member Expression", like [0] in x[0] – that's a member expression!

Look familiar? [[]][0] is a member expression.

So, we're good on syntax. [[]][0]++ is valid.

But, wait! Wait! Wait!

If [] is not a Reference, how could [[]][0] – which results in just [], remember! – possibly be considered a Reference so that PutValue(..) (described above) doesn't throw an error?

This is where things get just a teeny bit tricky. Hat tip to my friend Allen-Wirfs Brock, former editor of the JS spec, for helping me connect the dots.

The result of a member expression is not the value itself ([]), but rather a Reference to that value – see Step 8 here. So in fact, the [0] access is giving us a reference to the 0th position of that outer array, rather than giving us the actual value in that position.

And that's why it's valid to use [[]][0] as a left hand side expression: it actually is a Reference after all!

As a matter of fact, the ++ does actually update the value, as we can see if we were to capture these values and inspect them later:

var a = [[]];
a[0]++;

a;  // [1]

The a[0] member expression gives us the [] array, and the ++ as a mathematical expression will coerce it to a primitive number, which is first "" and then 0. The ++ then increments that to 1 and assigns it to a[0]. It's as if a[0]++ was actually a[0] = a[0] + 1.

A little side note: if you run [[]][0]++ in the console of your browser, it will report 0, not 1 or [1]. Why?

Because ++ returns the "original" value (well, after coercion, anyway – see step 2 here), not the updated value. So the 0 comes back, and the 1 gets put into the array via that Reference.

Of course, if you don't keep the outer array in a variable like we did, that update is moot since the value itself goes away. But it was updated, nonetheless. Reference. Cool!

Post-Corrected

I don't know if you appreciate JS or feel frustrated by all these nuances. But this endeavor makes me respect the language more, and renews my vigor to keep learning it even deeper. I think any programming language will have its nooks and crannies, some of which we like and some which drive us mad!

No matter your persuasion, there should be no disagreement that whatever your tool of choice, thinking more like the tool makes you better at using that tool. Happy JavaScript thinking!

If you're interested in working with JavaScript, check out our JavaScript Works job-board here.

Originally published on davidwalsh.name

Related Jobs

Related Issues

viebel / klipse-clj
viebel / klipse-clj
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • 1
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
  • $80
viebel / klipse
  • Open
  • 0
  • 0
  • Advanced
  • Clojure
  • $80
viebel / klipse
  • Started
  • 0
  • 2
  • Advanced
  • Clojure
  • $180
viebel / klipse
  • Started
  • 0
  • 1
  • Intermediate
  • Clojure
viebel / klipse
  • Started
  • 0
  • 3
  • Intermediate
  • Clojure
  • $80
viebel / klipse
  • 1
  • 0
  • Advanced
  • Clojure
  • $300

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through JavaScript Works average a 15% increase in salary.

Start with GithubStart with Stack OverflowStart with Email