演算子

演算子はよく利用する演算処理を記号などで表現したものです。 たとえば、足し算する + も演算子の一種です。これ以外にも演算子には多くの種類があります。

演算子は演算する対象をもちます。この演算子の対象のことを被演算子(オペランド)と呼びます。

次のコードでは、+演算子が値同士を足し算する加算演算を行っています。 このとき、+演算子の対象となっている12という2つの値がオペランドです。

1 + 2;

このコードでは+演算子に対して、前後に合計2つのオペランドがあります。 このように、2つのオペランドを取る演算子を二項演算子と呼びます。

// 二項演算子とオペランドの関係
オペランド1 演算子 オペランド2

また、1つの演算子に対して1つのオペランドだけをとるものもあります。 たとえば、次のような数値をインクリメントする++演算子は、前後どちらか一方にオペランドを置きます。

let num = 1;
num++;
// または
++num;

このように、1つのオペランドを取る演算子を単項演算子と呼びます。 単項演算子と二項演算子で同じ記号を使うことがあるため、呼び方を変えています。

この章では、演算子ごとにそれぞれの処理について学んでいきます。 また、演算子の中でも比較演算子は、JavaScriptでも特に挙動が理解しにくい暗黙的な型変換という問題と密接な関係があります。 そのため、演算子をひととおり見た後に、暗黙的な型変換と明示的な型変換について学んでいきます。

二項演算子

四則演算など基本的な二項演算子を見ていきます。

プラス演算子(+

2つの数値を加算する演算子です。

console.log(1 + 1); // => 2

JavaScriptでは、数値は内部的にIEEE 754方式の浮動小数点数として表現されています。 ( データ型とリテラルを参照) そのため、整数と浮動小数点数の加算もプラス演算子で行えます。

console.log(10 + 0.5); // => 10.5

マイナス演算子(-

2つの数値を減算する演算子です。

console.log(1 - 1); // => 0
console.log(10 - 0.5); // => 9.5

乗算演算子(*

2つの数値を乗算する演算子です。

console.log(2 * 8); // => 16
console.log(10 * 0.5); // => 5

除算演算子(/

2つの数値を除算する演算子です。

console.log(8 / 2); // => 4
console.log(10 / 0.5); // => 20

剰余演算子(%

2つの数値のあまりを求める演算子です。

console.log(8 % 2); // => 0
console.log(9 % 2); // => 1
console.log(10 % 0.5); // => 0
console.log(10 % 4.5); // => 1

[ES2016] べき乗演算子(**

2つの数値のべき乗を求める演算子です。 左オペランドを右オペランドでべき乗した値を返します。

// べき乗演算子(ES2016)で2の4乗を計算
console.log(2 ** 4); // => 16

べき乗演算子と同じ動作をするMath.powメソッドがあります。

console.log(Math.pow(2, 4)); // => 16

べき乗演算子はES2016で後から追加された演算子であるため、関数と演算子それぞれ存在しています。 他の二項演算子は演算子が先に存在していたため、Mathには対応するメソッドがありません。

単項演算子(算術)

単項演算子は、1つのオペランド受け取り処理する演算子です。

単項プラス演算子(+

単項演算子の+はオペランドを数値に変換します。

次のコードでは、数値の1を数値へ変換するため、結果は変わらず数値の1です。 +数値のように数値に対して、単項プラス演算子を付けるケースはほぼ無いでしょう。

console.log(+1); // => 1

また、単項プラス演算子は、数値以外も数値へと変換します。 次のコードでは、数字(文字列)を数値へ変換しています。

console.log(+"1"); // => 1

一方、数値に変換できない文字列などはNaNという特殊な値へと変換されます。

// 数値ではない文字列はNaNという値に変換される
console.log(+"文字列"); // => NaN

NaNは"Not-a-Number"の略称で、数値ではないがNumber型の値を表現しています。 NaNはどの値とも(NaN自身に対しても)一致しない特性があり、Number.isNaNメソッドを使うことでNaNの判定を行えます。

// 自分自身とも一致しない
console.log(NaN === NaN); // => false
// Number型である
console.log(typeof NaN); // => "number"
// Number.isNaNでNaNかどうかを判定
console.log(Number.isNaN(NaN)); // => true

しかし、単項プラス演算子は文字列から数値への変換に使うべきではありません。 なぜなら、Numberコンストラクタ関数やparseInt関数などの明示的な変換方法が存在するためです。 詳しくは「暗黙的な型変換」の章で解説します。

単項マイナス演算子(-

単項マイナス演算子はマイナスの数値を記述する場合に利用します。

たとえば、マイナスの1という数値を -1 と書くことができるのは、単項マイナス演算子を利用しているからです。

console.log(-1); // => -1

また、単項マイナス演算子はマイナスの数値を反転できます。 そのため、"マイナスのマイナスの数値"はプラスの数値となります。

console.log(-(-1)); // => 1

単項マイナス演算子も文字列などを数値へ変換します。

console.log(-"1"); // => -1

また、数値へ変換できない文字列などをオペランドに指定した場合は、NaNという特殊な値になります。 そのため、単項プラス演算子と同じく、文字列から数値への変換に単項マイナス演算子を使うべきではありません。

console.log(-"文字列"); // => NaN

インクリメント演算子(++

インクリメント演算子(++)は、オペランドの数値を+1する演算子です。 オペランドの前後どちらかにインクリメント演算子をおくことで、オペランドに対して値を+1した値を返します。

let num = 1;
num++;
console.log(num); // => 2
// 次のようにした場合と結果は同じ
// num = num + 1;

インクリメント演算子(++)は、オペランドの後ろに置くか前に置くかで、それぞれで評価の順番が異なります。

後置インクリメント演算子(num++)は、次のような順で処理が行われます。

  1. numの評価結果を返す
  2. numに対して+1する

そのため、num++が返す値は+1する前の値となります。

let x = 1;
console.log(x++); // => 1
console.log(x);   // => 2

一方、前置インクリメント演算子(++num)は、次のような順で処理が行われます。

  1. numに対して+1する
  2. numの評価結果を返す

そのため、++numが返す値は+1した後の値となります。

let x = 1;
console.log(++x); // => 2
console.log(x);   // => 2

この2つの使い分けが必要となる場面は多くありません。 そのため、評価の順番が異なることだけを覚えておけば問題ないといえます。

デクリメント演算子(--

デクリメント演算子(--)は、オペランドの数値を-1する演算子です。

let num = 1;
num--;
console.log(num); // => 0
// 次のようにした場合と結果は同じ
// num = num - 1;

デクリメント演算子は、インクリメント演算子と同様に、オペランドの前後のどちらかに置くことができます。 デクリメント演算子も、前後どちらに置くかでの評価の順番が変わります。

// 後置デクリメント演算子
let x = 1;
console.log(x--); // => 1
console.log(x);   // => 0
// 前置デクリメント演算子
let y = 1;
console.log(--y); // => 0
console.log(y);   // => 0

比較演算子

比較演算子はオペランド同士の値を比較し、真偽値を返す演算子です。

厳密等価演算子(===

厳密等価演算子は、左右の2つのオペランドを比較します。 同じ型で同じ値である場合に、trueを返します。

console.log(1 === 1); // => true
console.log(1 === "1"); // => false

また、オペランドがどちらもオブジェクトである時は、 オブジェクトの参照が同じである場合に、trueを返します。

次のコードでは、空のオブジェクトリテラル({})同士を比較しています。 オブジェクトリテラルは、新しいオブジェクトを作成します。 そのため、異なるオブジェクトを参照する変数を===で比較するとfalseを返します。

// {} は新しいオブジェクトを作成している
const objA = {};
const objB = {};
// 生成されたオブジェクトは異なる参照となる
console.log(objA === objB); // => false
// 同じ参照を比較している場合
console.log(objA === objA); // => true

厳密不等価演算子(!==

厳密不等価演算子は、左右の2つのオペランドを比較します。 異なる型または異なる値である場合に、trueを返します。

console.log(1 !== 1); // => false
console.log(1 !== "1"); // => true

===を反転した結果を返す演算子となります。

等価演算子(==

等価演算子(==)は、2つのオペランドを比較します。 同じデータ型のオペランドを比較する場合は、厳密等価演算子(===)と同じ結果になります。

console.log(1 == 1); // => true
console.log("str" == "str"); // => true
console.log("JavaScript" == "ECMAScript"); // => false
// オブジェクトは参照が一致しているならtrueを返す
// {} は新しいオブジェクトを作成している
const objA = {};
const objB = {};
console.log(objA == objB); // => false
console.log(objA == objA); // => true

しかし、等価演算子(==)はオペランド同士が異なる型の値であった場合に、 同じ型となるように暗黙的な型変換してから比較します。

そのため、次のような、見た目からは結果を予測できない挙動が多く存在します。

// 文字列を数値に変換してから比較
console.log(1 == "1"); // => true
// "01"を数値にすると`1`となる
console.log(1 == "01"); // => true
// 真偽値を数値に変換してから比較
console.log(0 == false); // => true
// nullの比較はfalseを返す
console.log(0 == null); // => false
// nullとundefeinedの比較は常にtrueを返す
console.log(null == undefined); // => true

意図しない挙動となることがあるため、暗黙的な型変換が行われる等価演算子(==)を使うべきではありません。 代わりに、厳密等価演算子(===)を使い、異なる型を比較したい場合は明示的に型を合わせるべきです。

例外的に、等価演算子(==)が使われるケースとして、nullundefinedの比較があります。

次のように、比較したいオペランドが null または undefined であることを判定したい場合に、 厳密等価演算子(===)では2度比較する必要があります。 等価演算子(==)ではnullundefinedの比較結果はtrueとなるため、一度の比較でよくなります。

const value = undefined; /* または null */
// === では2つの値と比較しないといけない
if (value === null || value === undefined) {
    console.log("valueがnullまたはundefinedである場合の処理");
}
// == では null と比較するだけでよい
if (value == null) {
    console.log("valueがnullまたはundefinedである場合の処理");
}

このように等価演算子(==)を使う例外的なケースはありますが、 等価演算子(==)は暗黙的な型変換をするため、バグを引き起こしやすいです。 そのため、仕組みを理解するまでは常に厳密等価演算子(===)を利用することを推奨します。

不等価演算子(!=

不等価演算子(!=)は、2つのオペランドを比較し、等しくないならtrueを返します。

console.log(1 != 1); // => false
console.log("str" != "str"); // => false
console.log("JavaScript" != "ECMAScript"); // => true
console.log(true != true);// => false
// オブジェクトは参照が一致していないならtrueを返す
const objA = {};
const objB = {};
console.log(objA != objB); // => true
console.log(objA != objA); // => false

不等価演算子も、等価演算子(==)と同様に異なる型のオペランドを比較する際に、暗黙的な型変換をしてから比較します。

console.log(1 != "1"); // => false
console.log(0 != false); // => false
console.log(0 != null); // => true
console.log(null != undefined); // => false

そのため、不等価演算子(!=)は、利用するべきではありません。 代わりに暗黙的な型変換をしない厳密不等価演算子(!==)を利用します。

大なり演算子/より大きい(>

大なり演算子は、左オペランドが右オペランドより大きいならば、trueを返します。

console.log(42 > 21); // => true
console.log(42 > 42); // => false

大なりイコール演算子/以上(>=

大なりイコール演算子は、左オペランドが右オペランドより大きいまたは等しいならば、trueを返します。

console.log(42 >= 21); // => true
console.log(42 >= 42); // => true
console.log(42 >= 43); // => false

小なり演算子/より小さい(<

小なり演算子は、左オペランドが右オペランドより小さいならば、trueを返します。

console.log(21 < 42); // => true
console.log(42 < 42); // => false

小なりイコール演算子/以下(<=

小なりイコール演算子は、左オペランドが右オペランドより小さいまたは等しいならば、trueを返します。

console.log(21 <= 42); // => true
console.log(42 <= 42); // => true
console.log(43 <= 42); // => false

ビット演算子

ビット演算子はオペランドを符号付き32bit整数に変換してから演算します。 ビット演算子による演算結果は10進数の数値を返します。

たとえば、9という数値は符号付き32bit整数では次のように表現されます。

console.log(0b0000000000000000000000000001001); // => 9
// Number#toStringメソッドを使うことで2進数表記の文字列を取得できる
console.log((9).toString(2)); // => "1001"

また、-9という数値は、ビッグエンディアンの2の補数形式で表現されるため、次のようになります。

console.log(0b11111111111111111111111111110111); // => 4294967287
// ゼロ桁埋め右シフトをしてからNumber#toStringで2進数表記を取得できる
console.log((-9 >>> 0).toString(2)); // => "11111111111111111111111111110111"

ビット論理積(&

論理積演算子(&)はビットごとのAND演算した結果を返します。

console.log(15     & 9);      // => 9
console.log(0b1111 & 0b1001); // => 0b1001

ビット論理和(|

論理和演算子(|)はビットごとのOR演算した結果を返します。

console.log(15     | 9);      // => 15
console.log(0b1111 | 0b1001); // => 0b1111

ビット排他的論理和(^

排他的論理和演算子(^)はビットごとのXOR演算した結果を返します。

console.log(15     ^ 9);      // => 6
console.log(0b1111 ^ 0b1001); // => 0b0110

ビット否定(~

単項演算子の否定演算子(~)はオペランドを反転した値を返します。 これは1の補数として知られている値と同じものです。

console.log(~15); // => -16
console.log(~0b1111); // => -0b10000

否定演算子(~)はビット演算以外でも使われていることがあります。

文字列(Stringオブジェクト)がもつindexOfメソッドは、マッチする文字列を見つけて、そのインデックス(位置)を返すメソッドです。 このindexOfメソッドは、検索対象が見つからない場合には-1を返します。

const string = "森森本森森";
// 見つかった場合はインデックスを返す
// JavaScriptのインデックスは0から開始するので2を返す
console.log(string.indexOf("本")); // => 2
// 見つからない場合は-1を返す
console.log(string.indexOf("火")); // => -1

否定演算子(~)は1の補数を返すため、~(-1)0となります。

console.log(~0); // => -1
console.log(~(-1)); // => 0

JavaScriptでは0も、if文ではfalseとして扱われます。 そのため、~indexOfの結果0となるのは、その文字列が見つからなかった場合だけとなります。 次のコードのような否定演算子(~)とindexOfメソッドを使ったイディオムが一部では使われていました。

const string = "森森木森森";
// indexOfメソッドは見つからなかった場合は -1 を返す
if (string.indexOf("木") !== -1) {
    console.log("木を見つけました");
}
// 否定演算子(`~`)で同じ動作を実装
// (~(-1)) は 0 となるため、見つからなかった場合はif文の中身は実行されない
if (~string.indexOf("木")) {
    console.log("木を見つけました");
}

ES2015では、文字列(Stringオブジェクト)にincludesメソッドが実装されました。 includesメソッドは指定した文字列が含まれているかを真偽値で返します。

const string = "森森木森森";
if (string.includes("木")) {
    console.log("木を見つけました");
}

そのため、否定演算子(~)とindexOfメソッドを使ったイディオムは、includesメソッドに置き換えられます。

左シフト演算子(<<

左シフト演算子は、numberbitの数だけ左へシフトします。 左にあふれたビットは破棄され、0のビットを右から詰めます。

number << bit;

次のコードでは、9を2ビット分だけ左へシフトしています。

console.log(     9 << 2); // => 36
console.log(0b1111 << 2); // => 0b111100

右シフト演算子(>>

右シフト演算子は、numberbitの数だけ右へシフトします。 右にあふれたビットは破棄され、左端のビットのコピーを左から詰めます。

number >> bit;

次のコードでは、-9を2ビット分だけ右へシフトしています。 左端のビットのコピーを使うため、常に符号は維持されます。

console.log((-9) >> 2); // => -3
// 0b11111111111111111111111111110111 >> 2; // => 0b11111111111111111111111111111101

ゼロ埋め右シフト演算子(>>>

ゼロ埋め右シフト演算子は、numberbitの数だけ右へシフトするのは右シフト演算子(>>)と同じです。 右にあふれたビットは破棄され、0のビットを左から詰めます。

次のコードでは、-9を2ビット分だけゼロ埋め右シフトしています。 左端のビットは0となるため、常に正の値となります。

console.log((-9) >>> 2); // => 1073741821
// 0b11111111111111111111111111110111 >>> 2; // => 0b00111111111111111111111111111101

代入演算子(=

代入演算子(=)は変数に対して値を代入します。 代入演算子については「変数と宣言」の章も参照してください。

let x = 1;
x = 42;
console.log(x); // => 42

また、代入演算子は二項演算子と組み合わせて利用できます。 +=-=*=/=%=<<=>>=>>>=&=^=|=のように、演算した結果を代入できます。

let num = 1;
num += 10; // num = num + 10; と同じ
console.log(num); // => 11

[ES2015] 分割代入(Destructuring assignment)

今までみてきた代入演算子は1つの変数に値を代入するものでした。 分割代入を使うことで、配列やオブジェクトの値を複数の変数へ同時に代入できます。 分割代入は短縮記法のひとつでES2015から導入された構文です。

分割代入は、代入演算子(=)を使うのは同じですが、左辺のオペランドが配列リテラルやオブジェクトリテラルとなります。

次のコードでは、右辺の配列の値を、左辺の配列リテラルの対応するインデックスに書かれた変数名へ代入します。

const array = [1, 2];
// aには`array`の0番目の値、bには1番目の値が代入される
const [a, b] = array;
console.log(a); // => 1
console.log(b); // => 2

これは、次のように書いたのと同じ結果になります。

const array = [1, 2];
const a = array[0];
const b = array[1];

同様にオブジェクトも分割代入に対応しています。 オブジェクトの場合は、右辺のオブジェクトのプロパティ値を、左辺に対応するプロパティ名へ代入します。

const object = {
    "key": "value"
};
// プロパティ名`key`の値を、変数`key`として定義する
const { key } = object;
console.log(key); // => "value"

これは、次のように書いたのと同じ結果になります。

const object = {
    "key": "value"
};
const key = object.key;

条件(三項)演算子(?:

条件演算子(?:)は三項をとる演算子であるため、三項演算子とも呼ばれます。

条件演算子は条件式を評価した結果がtrueならば、Trueの時処理する式の評価結果を返します。 条件式falseである場合は、Falseの時処理する式の評価結果を返します。

条件式 ? Trueの時処理する式 : Falseの時処理する式;

if文との違いは、条件演算子は式として書くことができるため値を返します。 次のように、条件式の評価結果により"A" または "B" どちらかを返します。

const valueA = true ? "A" : "B";
console.log(valueA); // => "A"
const valueB = false ? "A" : "B";
console.log(valueB); // => "B"

条件分岐による値を返せるため、条件によって変数の初期値が違う場合などに使われます。

次の例では、text文字列にprefixとなる文字列を先頭に付ける関数を書いています。 prefixの第二引数を省略したり文字列ではないものが指定された場合に、デフォルトのprefixを使います。 第二引数が省略された場合には、prefixundefinedが入ります。

条件演算子の評価結果は値を返すので、constを使って宣言と同時に代入できます。

function addPrefix(text, prefix) {
    // `prefix`が指定されていない場合は"デフォルト:"を付ける
    const pre = typeof prefix === "string" ? prefix : "デフォルト:";
    return pre + text;
}

console.log(addPrefix("文字列")); // => "デフォルト:文字列"
console.log(addPrefix("文字列", "カスタム")); // => "カスタム文字列"

if文を使った場合は、宣言と代入を分ける必要があるため、constを使うことができません。

function addPrefix(text, prefix) {
    let pre = "デフォルト:";
    if (typeof prefix === "string") {
        pre = prefix;
    }
    return pre + text;
}

console.log(addPrefix("文字列")); // => "デフォルト:文字列"
console.log(addPrefix("文字列", "カスタム")); // => "カスタム文字列"

論理演算子

論理演算子は基本的に真偽値を扱う演算子で、AND、OR、NOTを表現できます。

AND演算子(&&

AND演算子(&&)は、左辺の値の評価結果がtrueであるならば、右辺の評価結果を返します。 左辺の評価がtrueではない場合、右辺は評価されません。

このような値が決まった時点でそれ以上評価しないことを短絡評価(ショートサーキット)と呼びます。

const x = true;
const y = false;
// x -> y の順に評価される
console.log(x && y); // => false
// 左辺がfalsyであるなら、その時点でfalseを返す
// xは評価されない
console.log(y && x); // => false

AND演算子は、if文と組み合わせて利用することが多い演算子です。 次のように、valueがString型で かつ 値が"str"である場合という条件をひとつの式として書くことができます。

const value = "str";
if (typeof value === "string" && value === "str") {
    console.log(`${value} is string value`);
}
// if文のネストで書いた場合と結果は同じとなる
if (typeof value === "string") {
    if (value === "str") {
        console.log(`${value} is string value`);
    }
}

このときに、valueがString型でない場合は、その時点でfalseとなります。

短絡評価はif文のネストに比べて短く書くことができます。

しかし、if文が3重4重にネストしているのは不自然なのと同様に、 AND演算子やOR演算子が3つ4つ連続する場合は複雑で読みにくいコードです。 その場合は抽象化ができないかを検討するべきサインとなります。

OR演算子(||

OR演算子(||)は、左辺の値の評価結果がfalseであるならば、右辺の評価結果を返します。 AND演算子(&&)とは逆に、左辺がtrueである場合は、右辺を評価せずtrueを返します。

const x = true;
const y = false;
// xがtrueなのでyは評価されない
console.log(x || y); // => true
// yはfalseなのでxを評価した結果を返す
console.log(y || x); // => true

OR演算子は、if文と組み合わせて利用することが多い演算子です。 次のように、value0または1の場合にif文の中身が実行されます。

const value = 1;
if (value === 0 || value === 1) {
    console.log("valueは0または1です。");
}

NOT演算子(!

NOT演算子(!)は、オペランドの評価結果がtrueであるならば、falseを返します。

console.log(!false); // => true
console.log(!true);  // => false

NOT演算子は必ず真偽値を返すため、次のように2つNOT演算子を重ねて真偽値へ変換するという使い方も見かけます。

const string = "";
// 空文字はfalsyな値
console.log(!!string); // => false

このようなケースの多くは、比較演算子を使うなどより明示的な方法で、真偽値を得ることができます。 安易に!!による変換に頼るよりは別の方法を探してみるのがいいでしょう。

const string = "";
// 空文字でないことを判定
console.log(string.length > 0); // => false

グループ演算子(()

グループ演算子は複数の二項演算子が組み合わさった場合に、演算子の優先順序を明示できる演算子です。

たとえば、次のようにグループ演算子で囲んだ部分が最初に処理されるため、結果も変化します。

const a = 1;
const b = 2;
const c = 3;
console.log(a + b * c); // 7
console.log((a + b) * c); // => 9

演算子の優先順序はECMAScript仕様で定義されていますが、多様な演算子が出てきた場合に見分けるのは難しいです。 グループ演算子はもっとも優先度が高い演算子となります。 そのため、グループ演算子を使い優先順序を明示できます。

次のようなグループ演算子を使わずに書いたコードを見てみましょう。 atrueまたは、bかつctrueであるときに処理されます。

if (a || b && c) {
    // a が true または
    // b かつ c が true
}

ひとつの式に複数の種類の演算子が出てくると読みにくくなる傾向があります。 このような場合にはグループ演算子を使い、結合順を明示して書くようにしましょう。

if (a || (b && c)) {
    // a が true または
    // b かつ c が true
}

文字列結合演算子(+

数値にでてきたプラス演算子(+)は、文字列の結合に利用できます。

プラス演算子は、文字列を結合した文字列を返します。

const value = "文字列" + "結合";
console.log(value); // => "文字列結合"

カンマ演算子(,

カンマ演算子(,)は、カンマ(,)で区切った式を左から順に評価し、 最後の式の評価結果を返します。

次の例では、式1式2式3の順に評価され、式3の評価結果を返します。

式1, 式2, 式3;

これまでに、カンマで区切るという表現は、varによる変数宣言などでも出てきました。 左から順に実行する点ではカンマ演算子の挙動は同じものですが、構文としては似て非なるものです。

const a = 1, b = 2, c = a + b;
console.log(c); // => 3

一般にカンマ演算子を利用する機会は殆どないため、「カンマで区切った式は左から順に評価される」ということだけを知っていれば問題ありません。1

1. カンマ演算子を活用したテクニックとしてindirect callというものがあります。