Skip to content
A proposal to combine Logical Operators and Assignment Expressions
HTML
Branch: master
Clone or download
jridgewell Update notes (#13)
* Update notes

* Update index

* Update Stage

* Update notes to say "this expression"
Latest commit 9ee28e5 Feb 16, 2020
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitattributes Add spec text (#9) Jan 20, 2020
.gitignore
.npmrc Add spec text (#9) Jan 20, 2020
LICENSE Add spec text (#9) Jan 20, 2020
README.md Mention C# Feb 11, 2020
index.html Update notes (#13) Feb 16, 2020
package.json Add spec text (#9) Jan 20, 2020
spec.emu Update notes (#13) Feb 16, 2020

README.md

proposal-logical-assignment

A proposal to combine Logical Operators and Assignment Expressions:

// "Or Or Equals" (or, the Mallet operator :wink:)
a ||= b;
a || (a = b);

// "And And Equals"
a &&= b;
a && (a = b);

// "QQ Equals"
a ??= b;
a ?? (a = b);

Champions

Reviewers for Stage 3

Status

Current Stage: 2

Motivation

Convenience operators, inspired by Ruby's. We already have a dozen mathematical assignment operators, but we don't have ones for the often used logical operators.

function example(a) {
  // Default `a` to "foo"
  if (!a) {
    a = 'foo';
  }
}

If statements work, but terseness would be nice. Especially when dealing with deep property assignment:

function example(opts) {
  // Ok, but could trigger setter.
  opts.foo = opts.foo ?? 'bar'

  // No setter, but 'feels wrong' to write.
  opts.baz ?? (opts.baz = 'qux');
}

example({ foo: 'foo' })

With this proposal, we get terseness and we don't have to suffer from setter calls:

function example(opts) {
  // Setters are not needlessly called.
  opts.foo ??= 'bar'

  // No repetition of `opts.baz`.
  opts.baz ??= 'qux';
}

example({ foo: 'foo' })

Semantics

The logical assignment operators function a bit differently than their mathematical assignment friends. While math assignment operators always trigger a set operation, logical assignment embraces their short-circuiting semantics to avoid it when possible.

let x = 0;
const obj = {
  get x() {
    return x;
  },
  
  set x(value) {
    console.log('setter called');
    x = value;
  }
};

// This always logs "setter called"
obj.x += 1;
assert.equal(obj.x, 1);

// Logical operators do not call setters unnecessarily
// This will not log.
obj.x ||= 2;
assert.equal(obj.x, 1);

// But setters are called if the operator does not short circuit
// "setter called"
obj.x &&= 3;
assert.equal(obj.x, 3);

In most cases, the fact that the set operation is short-circuited has no observable impact beyond performance. But when it has side effects, it is often desirable to avoid it when appropriate. In the following example, if the .innerHTML setter was triggered uselessly, it could result in the loss of state (such as focus) that is not serialized in HTML:

document.getElementById('previewZone').innerHTML ||= '<i>Nothing to preview</i>';

See discussion of short-circuit semantics in #3. It also highlights differences already present in mathematical assignment operators in code like obj.deep[key++] += 1 vs obj.deep[key] = obj.deep[key++] + 1.

Related

You can’t perform that action at this time.