JavaScriptの「演算子」を全種類言えますか？　～演算子とは何かを完全に理解する～

JavaScriptプログラムの基本的な構成要素のひとつが**演算子**です。多くの方が普段のJavaScriptプログラミングで演算子を使っているでしょう。

では、あなたは「**演算子とは何か**」という問いに答えられますか？　演算子と演算子以外の違いはどこにあるのでしょうか？

演算子とは何かという定義は、人によって考え方が違うでしょう。筆者の個人的な考えとしては、演算子は「1つ以上の式から別の式を構成する構文を特徴づけるトークン」であると考えています。

しかし、JavaScriptには**仕様書**があります。仕様書はJavaScript (ECMAScript) に関する最も信頼できる情報源ですから、何が演算子で何が演算子でないのかについてもたいへん強力な基準を与えてくれることが期待できます。

そこで、この記事では**何が仕様書で演算子と呼ばれているのか**について全て解説します。併せて、演算子っぽいけど演算子とは呼ばれていないものについても解説します。この記事を読んで、「JavaScriptの演算子とは何か」について正確な理解を得ましょう。

ついでに、折角ですからそれぞれの演算子に対して簡単な解説を挟みます。初心者の方はJavaScriptの演算子を網羅的に理解する機会とし、中級者以上の方はこの機会に演算子について復習しましょう。

なお、演算子は英語で**operator**と呼ばれます。この記事では、仕様書でoperatorと呼ばれている構文を出てくるのが早い順に全て列挙します。この記事で参照するのは[Draft ECMA-262 / February 15, 2020 版](https://tc39.es/ecma262/)です。

## グルーピング演算子 `( )`

最初の演算子は`( )`です。これは`(a + b) * 2`とかに出てくる`( )`であり、計算の順序を定める効果を持ちます。

`( )`は演算子に見えないという方がいるかもしれませんが、[12.2.10 The Grouping Operator](https://tc39.es/ecma262/#sec-grouping-operator)でGrouping Operatorと呼ばれているので演算子に数えます。

## `new`演算子

`new`は、`new Array(10)`のような式に現れる演算子であり、クラス等のコンストラクタを呼びインスタンスを作る効果があります。この例の場合は`length`は10の`Array`インスタンスとなります[^note_array_exotic]。

個人的にはこれが演算子というのは認めがたいのですが、仕様書が演算子であると言っているので演算子です（[12.3.5 The new Operator](https://tc39.es/ecma262/#sec-new-operator)）

[^note_array_exotic]: 配列の場合は実際にはただのインスタンスではなくexotic objectが作られるのでちょっと特殊ですが。

## インクリメント・デクリメント演算子

`++`と`--`です。これは文句なしに演算子ですね。[12.4.4 Postfix Increment Operator](https://tc39.es/ecma262/#sec-postfix-increment-operator)～[12.4.7 Prefix Decrement Operator](https://tc39.es/ecma262/#sec-postfix-increment-operator)が該当します。仕様上は前置と後置が別々の演算子として定義されています。まあ構文が違いますから妥当ですね。

`foo++`や`++foo`は`foo`に1を足し、`foo--`や`--foo`は1を引きます。前置と後置の違いは返り値であり、前置の場合は計算の後の`foo`の値が、後置の場合は計算の前の`foo`の値が返ります。

## `delete`演算子

[12.5.3 The delete Operator](https://tc39.es/ecma262/#sec-delete-operator)で定義されている演算子で、`delete foo`や`delete obj.foo`のような構文を持ちます。（すでに`new`が出ていますが）演算子といっても必ずしも記号ではないという例ですね。

`delete`演算子は与えられた変数やプロパティを削除する演算子です。

### プロパティの削除

プロパティの削除は話が簡単です。`delete`演算子によって削除されたプロパティは文字通り存在しなくなります。次の例では最初`obj`は`{ foo: 123 }`でしたが、`delete`の後は`obj`は`{}`と同じになります。

```js
const obj = { foo: 123 };
console.log(obj.foo); // 123

delete obj.foo;

console.log(obj.foo); // undefined
console.log(obj.hasOwnProperty("foo")); // false
```

ただし、configurable属性がfalseに設定されたプロパティは削除できません。そのようなプロパティは`Object.defineProperty`などの手段で作ることができます（第3引数のオブジェクトで`configurable: true`を指定しなければデフォルト値の`false`となります）。

```js
const obj = {};
Object.defineProperty(obj, "foo", {
  value: 456
});
console.log(obj.foo); // 456

delete obj.foo; // エラーが発生（strictモードの場合）
```

`configurable`が`false`の場合は、プロパティは消えませんがエラーも起こりません。

### 変数の削除

`delete`演算子は変数も削除できます。しかし、皆さんがこの機能を使うのは非常にまれでしょう。なぜなら、strictモードでは`delete`で変数を消すのは禁止されており、文法エラーとして扱われるからです。

また、strictモードでなかったとしても、どんな変数でも消せるわけではありません。消すことができる変数はたいへん限られています。具体的には、消すことができるのは**グローバル変数**のみです。それも、`var`を使わずに宣言されたグローバル変数に限ります[^note_let_const]。ちなみに、strictモードでは`var`を使わずにグローバル変数を作ることはできない（存在しない変数にいきなり代入するとエラーが発生する）ので、strictモードでは`delete`は無意味となります。この意味でも、`delete`による変数の削除は非常に非strictモード的な機能であると言えます。

[^note_let_const]: 関数の中ではないトップレベルのコードにおいては、`var`で宣言された変数はグローバル変数として扱われます。一方、`let`や`const`で宣言された変数はグローバル変数として扱われません。

```js
// varを使わずに作られたグローバル変数
a = 123;
// varを使って作られたグローバル変数
var b = 456;

delete a;
delete b;

console.log(typeof a); // "undefined" (aが消えた)
console.log(typeof b); // "number" (bは消えていない)

function foo() {
  var abc = 123;
  console.log(abc); // 123
  delete abc;       // abc はグローバル変数ではないので消されない
  console.log(abc); // 123
}
foo();
```

ただし、`delete`が消すことができる変数はもう一つあります。それは**`eval`経由で作られた変数**です。この場合は、`var`で作られた変数であっても消すことができます。

```js
function foo() {
  eval("var abc = 123;");
  console.log(abc); // 123
  delete abc;
  console.log(abc); // abcが存在しないのでエラーが発生
}
```

たとえ`eval`で作られた変数であっても、上の例の`foo`の中で作られた変数`abc`は`foo`内のローカル変数となります。`eval`経由で作られた変数の場合、特別にローカル変数を`delete`演算子で消すことができるのです。

### `delete`演算子の返り値

`delete`演算子は**式**を作る構文であり、式ということは値があります。`delete`演算子の返り値は真偽値であり、基本的には「削除に成功したら`true`、失敗したら`false`」です。ただし、最初から存在しない変数を削除しようとしていた場合は`true`になるので要注意です。

全く使わない演算子である割に何だか奥が深いですね。

## `void`演算子

`void 式`という構文を持つ演算子で、式を評価するものの、結果を無視して常に`undefined`を返します（[12.5.4 The void Operator](https://tc39.es/ecma262/#sec-void-operator)）。

そんなもの何に使うのかと思いきや、実は**`undefined`を得る**という重要な使いみちがあります。`undefined`を使いたいとき、`void 0`と書けば3文字節約できます。

また、単に文字数を節約するだけでなく、**変数`undefined`が信頼できない**場面でも有効です。というのも、我々は`undefined`と書けば`undefined`という値を得ることができますが、これは`undefined`という名前のグローバル変数が`undefined`が入った状態で用意されているからです。ということは、`undefined`という名前の変数を作ってしまえば`undefined`と書いても`undefined`が得られないのです。`void`はこのような状況でも`undefined`を供給することができます。

```ts
{
  const undefined = "hi";

  console.log(undefined); // "hi"
  console.log(void 0); // undefined
}
```

例えば、TypeScriptで`??`演算子（左辺が`null`または`undefined`なら右辺を返し、それ以外なら左辺を返す）は次のようにトランスパイルされます。`void 0`が使われていることが分かりますね[^note_eqeq]。

```js
// トランスパイル前
const foo = bar ?? 0;
// トランスパイル後
const foo = (bar !== null && bar !== void 0 ? bar : 0);
```

[^note_eqeq]: `null`または`undefined`であることを判定するなら`bar != null`でいいのではと思われたかもしれませんが、`!=`を使うと[document.allが`document.all == null`と判定されてしまう](https://qiita.com/uhyo/items/cc0e6ee1bcce6792bfb0)のでまずいのです。

ちなみに、`null`は変数ではなくリテラルなので、`null`と書けば常に`null`であることが保証されます。

## `typeof`演算子

`typeof 式`とすると、その`式`の値に応じていろいろな文字列が得られる演算子です（[12.5.5 The typeof Operator](https://qiita.com/uhyo/items/cc0e6ee1bcce6792bfb0)）。プリミティブは細かに種類を判別することができますが、オブジェクトに関しては`"object"`と`"function"`の2種類の結果しかありません。プリミティブに対しては、`"string"`, `"number"`, `"boolean"`, `"bigint"`, `"symbol`", `"undefined"`という種類の結果が得られます。比較的有名な話ですが、`typeof null`は歴史的経緯により`"object"`となります。これを`"null"`にしようという試みもありましたが、頓挫しました。

存在しない変数に対して`typeof 変数`としてもランタイムエラーではなく`"undefined"`が得られることから、グローバル変数が存在するかどうかを判別する貴重な手段として重宝されています（これだと`undefined`が入っているグローバル変数が存在している可能性も残りますが、それが問題になることはほとんどありません）。

## 単項`+`演算子

ここからはしばらく演算子らしい演算子が続きます。

[12.5.6 Unary + Operator](https://tc39.es/ecma262/#sec-unary-plus-operator)で定義される単項`+`演算子は、`+式`の形で用いることで与えられた式の値を数値に変換します。

数値に変換するのは`Number(式)`でもできるのですが、短いプログラムが好きな人達には`+式`が好まれています。これはTypeScriptでも認められており、TypeScriptでは`+`に渡された値が`number`型以外でも型エラーとはなりません（結果は`number`型になります）。

```js
console.log(+"1e4"); // 10000
console.log(+true); // 1
console.log(+{ toString(){ return "-345" } }); // -345
```

ちなみに、与えられる式がBigIntの場合は`+式`と`Number(式)`で違いがあります。

```js
console.log(Number(12345n)); // 12345
console.log(+12345n); // ランタイムエラーが発生
```

これは、BigIntは`Number`という明示的な方法を使って数値に変換しなければいけないということです。そうなっている理由は、BigIntからNumberへの変換では数値の精度が落ちる可能性がありバグの元となるため、暗黙のうちにこれが行われるのは望ましくないからです。

## 単項`-`演算子

単項演算子（[12.5.7 Unary - Operator](https://tc39.es/ecma262/#sec-unary-minus-operator)）は`-式`という形で、`式`の符号を反転させた数値を得る演算子です。`-`はBigIntにも対応しており、符号が反転されたBigIntが返ります。一見対称性が高いように見える`+`と`-`ですが、BigIntに関してはこのような非対称性があるのです。

```js
const bignum = 12345n;
console.log(-bignum); // -12345n
console.log(+bignum); // ランタイムエラー
```

BigInt以外が渡された場合は`+`と同様に全部数値（Number）に変換されます（ただし、オブジェクトをプリミティブ値に変換してBigIntになった場合はBigIntとして扱われます）。

ちなみに、TypeScriptでは`-`演算子に`number`型・`bigint`型以外を与えるのはコンパイルエラーとなります。これも`+`と非対称的な点ですね。

## `~`演算子

ビット反転の演算子です（[12.5.8 Bitwise NOT Operator ( ~ )](https://tc39.es/ecma262/#sec-bitwise-not-operator)）。一見何も説明する必要が無さそうなビット反転演算子ですが、JavaScriptでは一筋縄ではいきません。二つほど説明すべきことがあります。

一つは、数値（Number）のときは**数値は32ビットとして扱われる**ということです。JavaScriptではそれ以上の数値も扱うことができますが、ビット演算の場合はそれ以上のビットは切り捨てられます。また、結果は符号あり32ビット整数として解釈されます。

```js
console.log(~0); // -1 (0xffffffff は2の補数表現で-1を表したものであるため）
console.log(~-1); // 0
console.log(~0x100000000); // -1 (32ビットを超えるので0と同様に扱われる）
```

もう一つはBigIntの場合についてです。`~`演算子はBigIntも扱うことができますが、BigIntは任意精度の整数であるためビット数という概念がなく、「全てのビットを反転する」などということができないのです。そのため、BigIntのビット反転演算は別の方法で定義されています。

[6.1.6.2.2 BigInt::bitwiseNOT ( *x* )](https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseNOT)によれば、`x`がBigIntの場合「`~x`の値は`-x-1n`である」と定義されています。例えば、`~123n`は`-124n`となります。これは、通常の数値が32ビット整数に収まっている場合の挙動に一致しますから、それに倣ったものです。別の言い方をすれば、BigInt値を無限のビット数を持つ値であるとみなしてビット演算を行なっているとも言えます。

## `!`演算子

これは言うことがあまりありません。与えられた式を真偽値に変換して真偽を反転させる演算子です（[12.5.9 Logical NOT Operator ( ! )](https://tc39.es/ecma262/#sec-logical-not-operator)）。`true`と`false`を逆にしたいときに使いましょう。また、値を真偽値に変換するのに`Boolean(値)`ではなく`!!値`として文字数を節約する人もいます。

## `**`演算子

ES2016で密かに追加された演算子で、累乗を意味します（[https://tc39.es/ecma262/#sec-exp-operator](https://tc39.es/ecma262/#sec-exp-operator)）。例えば`2 ** 3`は`8`です。また、この演算子は右結合で、`x ** y ** z`は`x ** (y ** z)`として解釈されます。

この演算子は構文に関して面白い点が1つほどあります。それは、`-2 ** 4`のような式が構文エラーとなることです。つまり、`**`の左に単項`+`演算子・単項`-`演算子を使えないのです。これは、`-2 ** 4`が`(-2) ** 4`なのか`-(2 ** 4)`なのか分かりにくいからです。エラーを避けるには、必要に応じてこのように括弧を補う必要があります。

ECMAScriptの比較的新しい機能では、このように解釈が曖昧な構文ができそうな場合に、どちらかに寄せるのではなくどちらも許さないというデザインが取られることがあります。`**`はその先駆けのひとつです。

`**`はNumberとBigIntのどちらにも対応していますが、BigIntは`**`の右が負の数の場合にランタイムエラーとなります。`**`の左が整数で右が負の数の場合結果が分数となり、BigIntでは表現できないからです。

ちなみに、JavaScriptでは0の0乗は1です。

## `*`, `/`, `%`演算子

乗法の演算子です。仕様書では[12.7 Multiplicative Operators](https://tc39.es/ecma262/#sec-multiplicative-operators)として3種類まとめて定義されています。算術演算子全てに言えることですが、NumberとBigIntを混ぜて演算することはできません。両辺が同じ型である必要があります。

割り算に関しては、0で割った場合の挙動がNumberとBigIntで異なります。Numberの場合は左辺の値に応じてInfinityやNaNといった値が発生しますが、BigIntの場合は0で割るとランタイムエラーです。

このようにBigIntの計算はランタイムエラーが発生しやすいものになっています。これはNaNなどに起因する変なバグをなるべくわかりやすく防ぎたいという方向性と、そもそもBigIntにはNaNとかInfinityの概念がないという現実によるものです。

## `+`, `-`演算子

こちらは単項ではなく二項演算子の`+`・`-`で、やはり仕様書では[12.8 Additive Operators](https://tc39.es/ecma262/#sec-additive-operators)で定義されています。特に何の変哲もありませんが、`+`は足し算だけでなく文字列の連結にも使われます。

## ビットシフト演算子

[12.9 Bitwise Shift Operators](https://tc39.es/ecma262/#sec-additive-operators)で定義される3つの演算子`<<`, `>>`, `>>>`です。ビット演算なので、Numberの場合には32ビット符号付き整数として扱われるのも`~`の場合と一緒です。ビットシフト演算子はNumberとBigIntで大きく扱いが異なる演算子で、似たような計算でもNumberとBigIntで異なる結果となります。

```js
console.log(1 << 33);   // 2
console.log(1n << 33n); // 8589934592n

console.log(4 << -1);   // 0
console.log(4n << -1n); // 2n

console.log(4 >> -2);   // 0
console.log(4n >> -2n); // 16n
```

BigIntの場合は話が単純で、ビットシフトはただ単に値に2の累乗数を左辺に掛けたり割ったりする計算となります。

一方Number型の場合は、まず右辺の数値（ビットシフトの距離を表す数値）が下位5ビットを取ることによって0〜31の整数に変換されます。

```js
console.log(123 << 2); // 246
console.log(123 << 32); // 123 (32は0に変換されるのでビットシフトは起こらない)
```

`>>`と`>>>`の違いは左辺が負の数のときに現れます。`>>`は符号を保ったままま右シフトが行われる一方、`>>>`は符号を無視します。また、`>>>`の結果は常に正の整数です。

```js
console.log(-5 >> 1);  // -3
console.log(-5 >>> 1); // 2147483645

console.log(-5 >> 0);  // -5
console.log(-5 >>> 0); // 4294967291
```

`>>`と`>>>`の挙動の違いは、最上位ビットの扱いと右ビットシフトにより空く部分をどうするかです。BigIntの場合は最上位ビットという概念がありませんが、これは**BigIntでは`>>>`をサポートしない**という方法で解決します。

```js
console.log(-5n >> 1n);  // -3n
console.log(-5n >>> 1n); // ランタイムエラーが発生
```


## 6種類の比較演算子

[12.10 Relational Operators](https://tc39.es/ecma262/#sec-relational-operators)では、`<`, `>`, `<=`, `>=`, `instanceof`, `in`という6種類の演算子がRelational Operators (比較演算子)として紹介されています。最初の4種類はいいとして、残りの2つが異質ですね。正直、この6種類をひとまとめに扱う意味がよく分かりませんが、仕様がそう言っているのですからこれらは仲間なのです。

とはいえ、まずは最初の4種類から見ていきます。

### 数値・文字列の比較演算子

`<`, `>`, `<=`, `>=`は、数値の比較だけでなく文字列同士の比較を行うことができます。両辺が文字列だった場合、文字同士をコードポイント[^note_code_unit]で比較することによる辞書順で大小判定が比較されます。文字列と数値の比較になった場合は数値に寄せられます。

[^note_code_unit]: JavaScriptの文字列はUTF-16でエンコーディングされているため正確にはコードユニットですが。

### `instanceof`演算子

`instanceof`演算子は演算子の中で最も長いものです。`obj instanceof Class`で、`obj`が`Class`のインスタンスであるかどうかを判定して真偽値を返します。インスタンスであるかどうかの判定は、`obj`のプロトタイプチェーンを辿ってコンストラクタに`Class`を持つものが存在するかどうかを調べることで行われます。

ただし、実は`instanceof`の挙動はクラス側の`[Symbol.hasInstance]`メソッドで制御できます。

```js
class Foo {
  static [Symbol.hasInstance](obj) {
    return obj.foo === 100;
  }
}
const obj = { foo: 0 };
console.log(obj instanceof Foo); // false

obj.foo = 100;
console.log(obj instanceof Foo); // true
```

### `in`演算子

`in`演算子はオブジェクトが指定した名前のプロパティを持つかどうか判定して真偽値を返すプロパティです。`Object.prototype.hasOwnProperty`とは異なり、プロトタイプチェーンを辿ってプロパティがあるかどうか判定します。

```ts
console.log("hasOwnProperty" in {}); // true
```

## 等価演算子

等価演算子は、[12.11 Equality Operators](https://tc39.es/ecma262/#sec-equality-operators)で定義されている`==`, `!=`, `===`, `!==`の4種類の演算子です。日本語で何と呼べば分からない演算子第一位（当社調べ）です。

基本的には`===`, `!==`だけ使っていれば事足りる場面がほとんどで、`==`や`!=`は避けるべきです。これは、`==`は両辺の型が違う場合に型変換によって型を揃えて比較しようとするからです。詳しいことはこちらの既存記事をご参照ください。

- [JavaScriptのプリミティブへの変換を完全に理解する](https://qiita.com/uhyo/items/44c2f79873de13186743)


## ビット演算の二項演算子

ビット演算の二項演算子は[12.12 Binary Bitwise Operators](https://tc39.es/ecma262/#sec-binary-bitwise-operators)で定義されています。もうすこし短い訳があればいいのですが、「ビット演算の二項演算子」というのはいささか直訳的ですね。

これらもビット演算なので、Numberの場合は32ビット整数に変換の上ビット演算が行われる一方、BigIntの場合はビット数の制限がありません。

```js
console.log((2 ** 32) | 1);    // 1
console.log((2n ** 32n) | 1n); // 4294967297n
```

## 二項論理演算子

二項論理演子と言われて何のことかお分かりでしょうか。これは[12.13 Binary Logical Operators](https://tc39.es/ecma262/#sec-binary-logical-operators)の直訳であり、`||`と`&&`のことです。また、ES2020からは`??`が追加されました。`??`はプロポーザル時代はNullish Coalescing Operator（nullish合体演算子）と呼ばれていましたが、仕様書にはこの用語は出てきません。`x ?? y`という式は*CoalesceExpression*と呼ばれているので、Coalescing OperatorとかCoalesce Operatorと呼ぶのはありかもしれません。

意味は今さらなので省略しますが、これらに共通する特徴は短絡評価です。つまり、左辺を先に評価して、結果が左辺となることが分かったなら右辺は評価されません。

結合の優先順位は`&&` > `||` > `??`ですが、`??`の右に`||`や`&&`が来ることはできません。

```js
w && x || y ?? z   // OK ((w && x) || y) ?? z と同じ意味

w && x ?? y || z   // 構文エラーが発生
(w && x ?? y) || z // OK ((w && x) ?? y) || z と同じ意味
```

これは、`||`や`??`は左辺の値が偽（あるいはnullish）の値だったときのフォールバックとして付加されることが多いところ、`x ?? y || z`としたときの挙動が分かりにくいことによる処置です。

## 条件演算子

条件演算子はオペランドを3つ持つ唯一の演算子であり、それゆえ三項演算子と呼ばれることもあるやつです。仕様書を見れば、正式名称が条件演算子 (Conditional Operator) であることは一目瞭然ですね（[12.14 Conditional Operator ( ? : )](https://tc39.es/ecma262/#sec-conditional-operator)）。

## 代入演算子

代入演算子は`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `<<=`, `>>=`, `>>>=`, `&=`, `|=`, `^=`, `**=`の総称です（[](https://tc39.es/ecma262/#sec-assignment-operators)）。

`&&=`や`||=`は存在しませんが、[これらを追加するというプロポーザル](https://github.com/tc39/proposal-logical-assignment)は存在します。2020年2月現在ではStage 2です。これらは`+=`などとは多少意味が違う点があります。具体的には、`a += b`は`a = a + b`と同じである一方、`a ||= b`は`a || (a = b)`と同じ意味になります。つまり、`a`が真なら代入すら行われないのです。このことは、セッタによって代入に副作用が仕込まれていた場合に目に見える違いとなって現れるのです。

## コンマ演算子

JavaScriptでは`,`は色々な場面で使われますが、実は演算子としての`,`も存在します。それがコンマ演算子（[12.16 Comma Operator ( , )](https://tc39.es/ecma262/#sec-comma-operator)）です。コンマ演算子は一番結合度の低い演算子となっています。

`x, y`という式は、`x`と`y`を順に評価して`y`の値を返します。`x`は評価こそされるもののその結果は使用されません。これは、何だか`void`演算子と似ているところがあります。コンマ演算子を我々が普段使うことはほとんどありませんが、適当なminifierを用いてソースコードを最小化するとコンマ演算子が結構使われているのを見ることができます。

無理やりコンマ演算子を使ってみるとこんな感じです。

```js
for (let i=0; i < 10; i++) console.log("i is"), console.log(i);
```

`for`文のボディ部分は`{ }`を使わなければ書ける文はひとつだけですが、コンマ演算子を用いると複数の関数呼び出しを一つの式で表すことができます。一つの式で表すことができればそれは式文というひとつの文になりますから、`{ }`無しで`for`文の中に複数の関数呼び出しを書くことができるのです。こうすると、`{ }`で関数呼び出しを並べるよりも文字数を2文字ほど省略できます。たいへん嬉しいですね。

コンマは他の構文要素でも使われる記号ですから、コンマ演算子が活躍できる場所は限られています。例えば、関数呼び出しの引数リストの中ではコンマ演算子は使えません。`,`は引数の区切りとして扱われるからです。無理やりコンマ演算子を使うには、`( )`を使ってひとつの式であると明示する必要があります。

```js
f(x, y);   // (x, y)というひとつの式を渡しているのではなくxとyという2つの式を渡している
f((x, y)); // (x, y)というひとつの式を渡している
```

以上でECMAScriptで演算子 (operator) と呼ばれているものを全て列挙できました。

## 演算子なのかどうか微妙なもの

仕様書はJavaScriptという言語を定める絶対的な基準となる文書ですが、人間が作るものですからミスもあります。仕様書には、演算子でないと思われるものを演算子と呼んでいるように見える箇所が2箇所あります。折角ですから、この記事で触れておきます。

**追記（2020/03/12）**: この記事を書いて以降に仕様書が改訂され、以下で紹介する2箇所では演算子 (operator) の語は使われなくなりました。

### `super`

[Table 7: Additional Essential Internal Methods of Function Objects](https://tc39.es/ecma262/#table-6)は関数オブジェクトが持つ内部スロットを定義しています（内部スロットについては[JavaScriptの「継承」はどう定義されるのか？　仕様書を読んで理解する](https://qiita.com/uhyo/items/b63ac11e8ec54d2c3a2b)で詳しく解説しています）。その2行目、[[Construct]]内部メソッドの説明に次のように書かれています（強調は筆者）。

> Creates an object. Invoked via the **new or super operators**. 

「`new`または`super`演算子」と書かれており、`super`が演算子であるかのような記述があります。一方、`super`は仕様書の[12.3.7 The super Keyword](https://tc39.es/ecma262/#sec-super-keyword)で定義されており、こちらでは「`super`キーワード」と呼ばれていますから`super`は演算子でないと思われます。

ちなみに、`super`はクラス宣言の中で使うことができる構文であり、コンストラクタの中の`super()`で親クラスのコンストラクタを呼び出したり、`super.foo`で親クラスのプロパティ・メソッドを参照することができます。

**追記（2020/03/12）**: この部分は“new operator or super call”と改訂され、superは演算子とは呼ばれなくなりました。

### Optional Chaining

Optional ChainingはES2020で追加された`?.`という構文で、`foo?.bar`は`foo.bar`と似た動作をしますが、`foo`が`null`や`undefined`のときはエラーにならずに結果が`undefined`となります。Optional Chainingについては以下の記事で詳しく解説しています。

- [そろそろJavaScriptに採用されそうなOptional Chainingを今さら徹底解説](https://qiita.com/uhyo/items/6cd88c0ea4dc6289387a)

仕様書では`?.`による構文はOptional Chainとだけ呼ばれており（[12.3.9 Optional Chains](https://tc39.es/ecma262/#sec-optional-chains)）、演算子とはされていません。しかしながら、一箇所だけ`?.`を演算子と呼んでいる箇所があります。それは[12.3.1.1 Static Semantics: Early Errors](https://tc39.es/ecma262/#sec-left-hand-side-expressions-static-semantics-early-errors)です。NOTEの中に次のような記述があります（強調は筆者）。

> ...so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without **optional chaining operator**:

`foo.bar`の`.`は演算子とはどこにも書かれていませんから、そこから類推すると`?.`が演算子とは考えにくいですね。上記の記述はミスであると考えられます。

**追記（2020/03/12）**: この部分は“...without optional chaining:”と改訂され、optional chainingは演算子とは呼ばれなくなりました。

## 演算子ではないもの

ここまでで触れられなかったものは、仕様書でまったく演算子と呼ばれていないので明らかに演算子ではありません。どのようなものが演算子でないのか確認しましょう。

### 関数呼び出しの`( )`

`func(1, 2, 3)`のような関数呼び出しに出てくる`( )`は演算子ではありません。個人的には`new Class(1, 2, 3)`の`new`が演算子であることを考えると少し違和感がありますが、我々ごときが感じる違和感など仕様書の前には塵ひとつほどの価値しかありません。

### 各種リテラル

次の式にあるような`{ }`や`[ ]`は演算子ではなく、リテラルと呼ばれています。

```js
const obj = { foo: 0 };
const arr = [1, 2, 3];
```

括弧だから演算子ではないというわけでもないのが難しいところです。最初に見たように`( )`は演算子でした。

### プロパティアクセス

`foo.bar`とか`foo?.bar`に出てくる`.`や`?.`は演算子と呼ばれていません。また、`foo[name]`のような形のプロパティアクセスで使われる`[ ]`もやはり演算子ではありません。

### `yield`・`await`

これらは`yield 式`や`await 式`という形で使うことができる式であり、一見すると演算子でもおかしくありません（構造としては`typeof 式`などと全く同じです）。

しかし、仕様ではこれらは演算子とはされず、[YieldExpression](https://tc39.es/ecma262/#prod-YieldExpression)や[AwaitExpression](https://tc39.es/ecma262/#prod-AwaitExpression)のように定義されていますから、「yield式」や「await式」と呼ぶのが正しそうです。

### スプレッド構文・レスト構文の`...`

ES2015以降、JavaScriptには`...`という構文がたびたび登場します。`...`はスプレッド構文とレスト構文に大別され、前者は配列リテラルやオブジェクトリテラルの中、そして関数呼び出しの引数リストの中で使われる一方、後者は分割代入の配列・オブジェクトパターンや、関数宣言時の引数リストで使われます。

`...`は「スプレッド演算子」のように呼ぶ人が多くいるものの、仕様書では全く演算子と呼ばれていません。`...`を演算子と呼んでいた方はぜひ今日から改めましょう。

これが演算子ではない理由は、「式を作らない」からであると推察しています。すなわち、`...`は配列リテラルなど、他の構文の一部としてしか使うことができず、単独で式をなすものではないのです。

```js
const arr = [...foo]; // ←これはOK
const arr = ...foo;   // ←こんな構文は無い
```

### 変数宣言の`=`

先ほど解説した通り`=`は代入演算子の一種ですが、実は変数宣言のときの`=`は厳密には代入演算子ではありません。

```js
let foo = 123; // ←この = は代入演算子ではない
foo = 456;     // ←この = は代入演算子
```

前者は変数宣言の構文の一部（[Initializer](https://tc39.es/ecma262/#prod-Initializer)）として、代入演算子とは別個に定義されているのです。また、関数引数などのデフォルト値の定義にも`=`が使われますが、こちらもInitializerであり代入演算子ではありません。

実際に仕様書を見ると、[Initializerの定義](https://tc39.es/ecma262/#prod-Initializer)には次のよう書かれています（画像で引用）。

![initializer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233667/8e56f08b-a8fc-e323-9f5d-9cd5e8d8d54a.png)

一方で、代入演算子の`=`は[AssignmentExpressionの定義](https://tc39.es/ecma262/#prod-AssignmentExpression)の中で定義されています（画像で引用）。

![assignment-expression.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233667/2bc312ed-b36e-b1e5-9423-2c8602aa478a.png)

この2つを見ると、それぞれに`=`が使われており、同じ`=`という記号でも出自を異にするものであることが分かります。我々が普段`=`を使う際はそんなことをいちいち気にしなくても何とかなってしまうのですが。

## まとめ

この記事ではECMAScriptに存在する「演算子」を全種類（ES2020時点）解説しました。併せて、演算子っぽいが演算子ではないものも解説しました。

**結局演算子と演算子でないものの違いは何なのか**という問いについては、残念ながら単純明快な答えはありません。演算子は必ず式を作るという点は間違い無さそうですが、式を作るなら何でも演算子というわけではありません（`foo.bar`の`.`や`await`式の`await`は演算子ではありませんでしたね）。演算子であると定義されているものが演算子であると言うほか無いのです。

いずれにせよ、どれが演算子でどれが演算子でないかを丸暗記することに意味はありません。プログラムを書く身としては、どんな方法でもいいのでJavaScriptの構文を理解し、使いこなせるようになることが重要なのです。

その一方で、仕様書はJavaScriptプログラミングにおいて唯一完全に信頼できる情報源であり、質の高いプログラムを書くにあたって仕様書が果たす役割は小さくありません。ですから、JavaScriptの解説記事を書くような場合には演算子とそれ以外の区別ができていない（＝仕様書を理解していない）と非常に信頼性が低くなります。皆さんもJavaScriptの解説記事を書くときはぜひ正確性に気をつけましょう。また、JavaScriptの解説記事を読む際も演算子をひとつの試金石としてみるのはいかがでしょうか。