Advanced Static Types in TypeScript

This course explores the capabilities of TypeScript’s type system and shows how to use advanced static types in practice.

Watch the Course

The Comma Operator in JavaScript

I've been writing JavaScript for almost ten years now, yet it was not until recently that I discovered the comma operator. As part of my Bachelor's thesis, I implemented a control flow analyzer for JavaScript programs. Naturally, I had to read the language specification in order to understand how various statements and expressions are evaluated, which is where I stumbled upon the comma operator.

Comma Usage in JavaScript

Let's take a second and look at some JavaScript language constructs whose syntax requires us to write commas. We use commas to …

  • declare multiple variables at once: var x = 0, y = 0;
  • list elements within array literals: [4, 8, 15, 16, 23, 42]
  • separate properties of object literals: { min: 10, max: 128 }
  • define multiple function parameters: function add(a, b) { return a + b; }
  • call a function with multiple arguments: add(3, 5)
  • destructure arrays: const [lower, upper] = [0, 1];
  • destructure objects: const { min, max } = { min: 10, max: 128 };
  • import multiple module members: import { open, close } from "fs";
  • export multiple module members: export { mkdir, rmdir };

All of the above examples are syntactically correct and contain a comma, but none of them makes use of the actual comma operator. So what is this mysterious operator I'm talking about?

The Comma Operator

It turns out I had already been using the comma operator without knowing about it. Check out the following for-loop:

for (var x = 0, y = 10; x <= 10; x++, y--) {
    // ...
}

That's the comma operator, right there in the third component of the for-loop's header, separating the two expressions x++ and y--. In the language grammar, it's specified that a for-loop can have an optional expression as its update component, but not multiple ones. Therefore, x++, y-- must be a single expression — and it is!

All the glory details are described in section 12.15 Comma Operator. In short, the comma operator evaluates each of its operands (from left to right) and finally returns the value of the rightmost operand. It can be used anywhere an expression is expected, though parentheses are necessary in some places to resolve grammar ambiguities.

Example #1: Incrementing + Assigning + Returning

Here's a real-world example, found in the scanner of the TypeScript compiler:

return pos++, token = SyntaxKind.GreaterThanToken;

First, the expression pos++ is evaluated, causing the pos variable to be incremented. Second, the token variable is assigned the value SyntaxKind.GreaterThanToken. Finally, the comma operator returns the value of its rightmost operand (in this case the new value of the token variable), which is passed as an argument to the return statement.

The above one-liner is semantically equivalent to these three separate statements:

pos++;
token = SyntaxKind.GreaterThanToken;

return token;

Note that I don't recommend to use the comma operator just to save a couple of lines or keystrokes. Not every JavaScript developer knows about the semantics of this language construct, which probably causes confusion and does more harm than good. In the context of the TypeScript scanner, however, the usage of the comma operator is probably acceptable because the developers know JavaScript inside-out.

Example #2: Computed Property Keys

The previous example could've easily been written another way, which is why I went looking for a more plausible use case of the comma operator. I found one in the transpiled code that the TypeScript compiler generates when compiling an object literal with computed property keys, like this one:

const map = {
    [1 << 0]: "foo",
    [1 << 1]: "bar",
    [1 << 2]: "baz"
};

Since ECMAScript 5 doesn't have computed property keys, the TypeScript compiler emits the following down-leveled code when targeting ES5:

var map = (_a = {},
    _a[1 << 0] = "foo",
    _a[1 << 1] = "bar",
    _a[1 << 2] = "baz",
    _a
);
var _a;

As you can see in the last line, the compiler introduces a new local variable _a. The variable declaration gets hoisted to the top of the containing function so it's accessible in the lines above as well. Within the parenthesized expression, _a is first assigned an empty object literal that represents the map. Afterwards, each property key is computed and used to assign the three string values. Finally, the fifth and last operand of the comma operator is _a, the map itself, which is returned and assigned to the map variable.

Since object literals are expressions, emitting five statements is not an option for the TypeScript compiler. The above parenthesized value can occur everywhere an expression is expected, whereas statements can only appear in statement position.

I hope this post shed some light on the comma operator. Use it responsibly!

Learn Node