ES2021: Logical assignment operators

[2020-06-11] dev, javascript, es2021
(Ad, please don’t block)

The ECMAScript proposal “Logical assignment operators” (by Justin Ridgewell and Hemanth HM) introduces the following compound assignment operators:

  • a ||= b
  • a &&= b
  • a ??= b

Existing compound assignment operators  

JavaScript already has the following compound assignment operators:

  • Arithmetic assignment operators: += -= *= /= %= **=
  • Bitwise assignment operators: &= ^= |=
  • Bitwise shift assignment operators: <<= >>= >>>=

Each of these assignment operators works as follows:

  • The expression: a op= b
  • is equivalent to: a = a op b

Recap: short circuiting  

Before we can examine the proposed operators, we have to take a brief detour and learn about short-circuiting.

The logical operator ||, &&, ?? all short-circuit – their second operands are only evaluated if their first operands don’t already determine the result:

Operator Equivalent to
a ¦¦ b a ? a : b
a && b !a ? a : b
a ?? b a !== undefined && a !== null ? a : b

The proposed logical assignment operators  

Logical assignment operators work differently from other compound assignment operators:

Assignment operator Equivalent to Only assigns if a is
a ¦¦= b a ¦¦ (a = b) Falsy
a &&= b a && (a = b) Truthy
a ??= b a ?? (a = b) Nullish

Why is a ||= b equivalent to the following expression?

a || (a = b)

Why not to this expression?

a = a || b

The former expression has the benefit of short-circuiting: The assignment is only evaluated if a evaluates to false. Therefore, the assignment is only performed if it’s necessary. In contrast, the latter expression always performs an assignment.

Example: using ??= to add missing properties  

const books = [
  {
    isbn: '123',
  },
  {
    title: 'ECMAScript Language Specification',
    isbn: '456',
  },
];

// Add property .title where it’s missing
for (const book of books) {
  book.title ??= '(Untitled)';
}

assert.deepEqual(
  books,
  [
    {
      isbn: '123',
      title: '(Untitled)',
    },
    {
      title: 'ECMAScript Language Specification',
      isbn: '456',
    },
  ]);

Example: breaking up an expression  

Consider the following function which returns an expression spread out across multiple lines:

function canContainNumber(value) {
  return typeof value === 'number'
    || typeof value === 'bigint'
    || typeof value === 'string'
  ;
}

assert.equal(canContainNumber(''), true);
assert.equal(canContainNumber(Symbol()), false);

This expression can be broken up as follows:

function canContainNumber(value) {
  let result = false;
  result ||= typeof value === 'number';
  result ||= typeof value === 'bigint';
  result ||= typeof value === 'string';
  return result;
}

Note: There are other, probably better, ways to improve the initial code. E.g., a switch statement.