【STEP.09】
JavaScriptの演算子の使い方をマスターしよう!

演算子とは

演算子(Operator:オペレーター)とは、与えられた変数/リテラルに対して、あらかじめ決められたなんらかの処理を行うための記号です。

たとえば、「=」「,」「-」などは、すべて演算子です。

また、演算子によって処理される変数/リテラルのことを、オペランド (Operand:被演算子)と呼びます。

オペレーターとオペランドとは

算術演算子

算術演算子とは

算術演算子とは、「+」「-」「*」「/」「%」など計算に使われる記号の総称のことをいいます。

代数演算子と呼ばれることもあります。

JavaScriptで利用可能な主な算術演算子

JavaScriptで利用可能な主な算術演算子は次に掲げる表の通りです。

【主な算術演算子】
演算子概要
+加算
4 + 6     // 結果:10
減算
10 - 5    // 結果:5
*乗算
5 * 6     // 結果:30
/除算
15 / 5    // 結果:3
%剰余
10 % 3    // 結果:1
**累乗
ES2016
2 ** 3    // 結果:8

加算演算子(+)は文字列を連結する

加算演算子(+)は、オペランド(被演算子)の型によって挙動が変化する点に要注意です。

具体的には、オペランドのいずれかが文字列である場合、これを連結します。

console.log('YOKOHAMA.' + 'ryusei');
// 結果:YOKOHAMA.ryusei

console.log('10' + '11');
// 結果:1011

console.log(8 + '5');
// 結果:85

let today = new Date();
console.log('日付:' + today);
// 結果:日付:Mon Nov 04 2019 12:16:10 GMT+0900 (日本標準時) 

console.log('10' + '11');」のように、見た目が数値であっても暗黙的に数値と見なされません。

オペランドの片方が数値であっても同様です。(console.log(8 + '5');

let today = new Date();」のようにオブジェクトが渡された場合には、オブジェクトを文字列形式に変換したうえで連結します(どんな文字列が生成されるかは、オブジェクトによって異なります)。

「i++」と「++i」の違い

++演算子はインクリメント演算子とも呼ばれ、オペランドに対して1を加算した結果を返します。

つまり、以下の結果はいずれも同じ意味です。

i++;
++i;
i = i + 1;

ただし、「i++」「++i」は、その結果を他の変数に代入する場合に結果が変化するので、注意する必要があります。

let i = 10;
let j = i++;

console.log(i);
// 結果:11
console.log(j);
// 結果:10	

let i = 10;
let j = ++i;

console.log(i);
// 結果:11
console.log(j);
// 結果:11

i++」では変数jに代入してから加算する(後置演算)のに対して、「++i」では加算してから変数jに代入しています。(前置演算)

前置演算,後置演算,xに1を加算して代入,x+1の結果を代入,xの値を代入

i--」「--i」、変数の値を1減算するデクリメント演算子でも、同じ関係が成り立ちます。

let i = 10;
let j = i--;

console.log(i);
// 結果:9
console.log(j);
// 結果:10

let i = 10;
let j = --i;

console.log(i);
// 結果:9
console.log(j);
// 結果:9

--iとi--の違い

「0.1*3」が0.30000000000000004になる理由

小数点を含んだ演算は、時として意図した結果を得られない場合があります。

console.log(0.1*3);
// 結果:0.30000000000000004

これは、JavaScriptが内部的には数値を(10進数ではなく)2進数で演算しているがために発生する誤差です。

10進数ではごく単純に表せる0.1という数値も、2進数では0.00011001100…と無限循環小数になってしまうのです。

結果、「0.1*3」のような一見単純な演算ですら、正しい結果を得ることができません。

同様に、以下の等式はJavaScriptでは偽(false)となります。

console.log(0.1*3 === 0.3);
// 結果:false

小数点を含む演算で厳密に結果を得る必要がある場合、あるいは、値の比較を行う場合には、以下のようにします。

  1. 値をいったん整数にしてから演算する
  2. (1)の結果を再び小数に戻す

たとえば上の例であれば、以下のようにすることで正しい結果を得られます。

console.log(((0.1 * 10) * 3) / 10);
                  ↑ 整数にしてから演算
// 結果:0.3

比較する場合も同じです。

console.log((0.1 * 10) * 3 === 0.3 * 10);
// 結果:true

値を何倍にするかは、有効桁数によって判断します。

例えば0.2351という値で小数点以下2桁までを保証したいならば、以下のようにします。

  1. 100倍して23.51としたものを演算する
  2. 最終的な結果を小数点以下で四捨五入する
  3. (2)の結果を100で除算して、再び小数点数に戻す

複合代入演算子

複合代入演算子とは

複合代入演算子とは、左辺と右辺の値を演算した結果を左辺に代入するための演算子で、以下のコードは意味的に等価となります。(●は複合代入演算子として利用できる、任意の算術/ビット演算子)

a ●= b  ⇔  a = a ● b

JavaScriptで利用可能な主な複合代入演算子

JavaScriptで利用可能な主な複合代入演算子は、次に掲げる表の通りです。

【主な複合代入演算子】
演算子概要
=変数などに値を代入
x = 1
+=左辺の値に右辺の値を加算した結果を代入
x=3; x += 2
// 結果:5
-=左辺の値から右辺の値を減算した結果を代入
x=3; x -= 2
// 結果:1
*=左辺の値に右辺の値を乗算した結果を代入
x=3; x *= 2
// 結果:6
/=左辺の値を右辺の値で除算した結果を代入
x=3; x /= 2
// 結果:1.5
%=左辺の値を右辺の値で除算した余りを代入
x=3; x %= 2
// 結果:1
&=左辺の値を右辺の値で論理積演算した結果を代入
x=10; x &= 5
// 結果:0
|=左辺の値を右辺の値で論理和演算した結果を代入
x=10; x |= 5   
// 結果:15
^=左辺の値を右辺の値で排他的論理和演算した結果を代入
x=10; x %= 5
// 結果:15
<<=左辺の値を右辺の値だけ左シフトした結果を代入
x=10; x <<= 1
// 結果:20
>>=左辺の値を右辺の値だけ右シフトした結果を代入
x=10; x >>= 1
// 結果:5
>>>=左辺の値を右辺の値だけ右シフトした結果を代入
x=10; x >>>= 2
// 結果:2

「基本型」と「参照型」

JavaScriptのデータ型は、次に掲げる表のように「基本型」と「参照型」に分類できます。

【JavaScriptのデータ型】
分類データ型概要
基本型数値型(number)±4.94065645841246544×10-324
~±1.79769313486231570×10-308
文字列型(string)クォートで囲まれた0個以上の文字
真偽型(boolean)true(真)/false(偽)
シンボル型(symbol)
ES2015
シンボルを表す
特殊型(null/undefined)値が定義されていない、存在しないことを表す
参照型配列(array)データの集合(各要素はインデックス番号で取得)
オブジェクト(object)データの集合(各要素は名前で取得)
関数(function)連なった処理のかたまり

両者の違いは「値を変数に格納する方法」です。

まず、基本型は値そのものを変数に格納します。

対して、参照型は、値を実際に格納しているメモリ上のアドレス(=参照値)を変数に格納します。

基本型と参照型の違い

その特性上、「基本型」と「参照型」とでは、以下のような違いが生じます。

「基本型」と「参照型」の違い

「=」演算子で他の変数に代入した場合

以下は、基本型/参照型の変数を=演算子で、別の変数に代入する例です。

// 基本型
let i = 10;
let j = i;
i = 15;

console.log(j);
// 結果:10

// 参照型
let m = ['赤','黄','青'];
let n = m;
m[2] = '緑';

console.log(n);
// 結果:["赤","黄","緑"]

まず、基本型の代入は直感的です。(let j = i;

基本型の値は変数にそのまま格納されるので、変数iから変数jに代入する際にも、値がコピーされるだけです。(このような値の渡し方を値による代入といいます。)

よって、コピー元の値が変更されたとしても(この場合は変数i)、コピー先となる変数jに影響が及ぶことはありません。

一方、参照型では変数に格納されているのはアドレスです。(let n = m;

そのため、代入によってもコピーされるのはアドレス(格納先)の情報だけです。

参照している値そのものがコピーされるわけではありません。

代入元と代入先とで、結果的に同じ値を参照するということです。

結果、代入元の変数mを変更すると、代入先の変数nにも影響が及ぶことになります。

このような値の渡し方を参照による代入といいます。

基本型,互いに別物なので、片方の変更はもう片方には影響しない,参照型,参照(格納先のアドレス)をコピー,互いに同じ場所を見ているので、片方の変更はもう片方にも影響する,値による代入とは,値そのものを代入すること,参照による代入とは,値を格納している参照先の情報を代入すること

「==」演算子で比較した場合

以下は、基本型/参照型の変数を==演算子で、互いに比較する例です。

// 基本型
let i = 10;
let j = 10; 

console.log(i==j);
// 結果:true

// 参照型
let m = ['赤','黄','青'];
let n = ['赤','黄','青'];
m[2] = '緑';

console.log(m==n);
// 結果:false 

考え方は先ほどと同じです。

基本型(console.log(i==j);)では、値同士を単純に比較するのに対して、参照型(console.log(m==n);)では参照値(メモリー上のアドレス)を比較します。

そのため、見かけ上等しいように見えても、それぞれが異なるオブジェクトであれば==演算子はfalseを返します。

参照型を定数に代入した場合の注意点

定数を「変更できない」と理解していると、思わぬ落とし穴にはまることがあります。

なぜなら、定数の本質は「再代入できない」だからです。

まず、基本型の例から見てみます。

// 基本型
const tax = 1.08;
tax = 1.10;
// 結果:エラー

これはなんら問題ありません。

基本型では、値を変更することは、すなわち再代入することだからです。

const制約によって、上のコードはエラーとなります。

しかし、参照型の場合は事情が変化します。

// 参照型
const data = [1,2,3]; 

data = [4,5,6]; 

data[0] = 10;

data = [4,5,6]; 」は配列そのものの再代入なのでエラーですが、「data[0] = 10;」はそのまま動作します。

元の配列はそのままに、その内容だけを書き換えられているからです。

変数の内容(参照)は変化しないので、const違反とは見なされないのです。

参照型の定数は必ずしも変更できないわけではないことを理解しておきましょう。

比較演算子

比較演算子とは

比較演算子は、左辺と右辺を所定のルールで比較し、その結果をtruefalseとして返します。

条件分岐、繰り返し構文などと併せて利用するのが一般的です。

JavaScriptで利用可能な主な比較演算子

JavaScriptで利用可能な主な比較演算子は以下の通りです。

【主な比較演算子】
演算子概要
==左辺と右辺の値が等しい場合はtrue
3 == 3
// 結果:true
!=左辺と右辺の値が等しくない場合はtrue
3 != 4
// 結果:true
<左辺が右辺より小さい場合はtrue
3 < 5
// 結果:true
<=左辺が右辺以下の場合はtrue
3 <= 3
// 結果:true
>左辺が右辺より大きい場合はtrue
6 > 3
// 結果:true
>=左辺が右辺以上の場合はtrue
7 >= 3
// 結果:true
===左辺と右辺の値が等しくてデータ型も同じ場合はtrue
3 === 3
// 結果:true
!==左辺と右辺の値が等しくない場合、もしくはデータ型が異なる場合はtrue
3 !== 3
// 結果:false
?:「条件式?式1:式2」。条件式がtrueの場合は式1を、falseの場合は式2を返す。
(1===1) ? true : false
// 結果:true

「==」と「===」の違い

==演算子は比較する値同士を「なんとか等しいと見なせないか」、JavaScriptがあれこれ世話を焼いてくれる演算子です。

具体的には、オペランドが(例えば)文字列と数値である場合にも、内部的にはデータ型を変換して比較してくれます。

したがって、以下の比較式はいずれもtrueとなります。

console.log(true == 1);
console.log('1.414E3' == 1414);
console.log('0x10' == 16);

論理型/文字列型/数値型が混在している場合は、それぞれが数値に変換されたうえで比較されます。

true/falseであれば1/0に、1.414E3、0x10のような文字列はそれぞれ指数表現、16進数表現として数値に、それぞれ変換されます。

ただし、このような自動変換が余計なお世話になる場合があります。

先ほどの「console.log('1.414E3' == 1414);」、「console.log('0x10' == 16);」にせよ、E、xが意味を持たないアルファベットであるとしたら、==演算子が勝手に変換してしまうのは不都合です。

一般的なアプリでは、==演算子の寛容さはかえって「余計なお世話」になる場合が多いので、原則として==演算子は利用してはいけません。

代わりに利用するのが===演算子です。

===はデータ型を勝手に決めないという他は、==演算子と同じく動作します。

そのため、以下の結果はいずれもfalseとなります。

console.log(true === 1);
console.log('1.414E3' === 1414);
console.log('0x10' === 16);

ただし、===演算子では「'100'」と「100」のように、人間の目には一見して同じに見える値でも異なるものと見なされてしまうので注意が必要です。

===演算子での比較に際しては、オペランドを意図したデータ型に明示的に変換する必要があります。

console.log('100' === 100);
// 結果:false(型が不揃い)
console.log(Number('100') === 100);
// 結果:true(型が一致)

この関係は、不等価演算子(!=)と非同値演算子(!==)でも同様です。

MEMO
falsyな値
if/whileなど条件式を要求する制御構文においては「if(flag == true)」とする代わりに「if(flag)」、「if(flag == false)」とする代わりに「if(!flag)」とする書き方がよく用いられます。

条件式の文脈では、変数(ここではflag)が暗黙的にboolean値に変換されるため、true/falseとさらに比較するのは無駄なことです。

以下に、暗黙的な型変換でfalseと見なされる値をまとめます。
  • 空文字列('')
  • 数値0、NaN
  • null、undefined
それ以外の値はすべてtrueです。

条件演算子

条件演算子?:を利用することで、条件式のtruefalseに応じて値を切り替えることができます。

条件演算子の構文は次の通りです。

条件演算子
cond ? t_exp : f_exp
cond     条件式
t_exp    条件式がtrueの時の値
f_exp    条件式がfalseの時の値

たとえば以下は、変数pointが70以上の場合に「Clear!」、さもなければ「Failed...」というメッセージを表示する例です。

let point = 75;
console.log((point >= 70) ? 'Clear!' : 'Failed...') 
// 結果:Clear!

同じことはif命令でも表現できますが、単に条件式に応じて値を振り分けたいという場合には、条件演算子のほうがシンプルにコードを記述できます。

MEMO
三項演算子
演算子は、オペランドの個数によって単項演算子、二項演算子、三項演算子に分類できます。

ほとんどの演算子は、「2+3」のように演算子の前後にオペランドを指定する二項演算子です。

逆に、三項演算子は条件演算子だけです。

単項演算子は「++」「--」「!」などです。

「-」のように用途によって単項/二項演算子となるものもあります。(「-1」は単項演算子ですが、「2-1」は二項演算子です。)

論理演算子

論理演算子とは

論理演算子は、複数の条件式(または論理値)を論理的に結合し、その結果をtruefalseとして返します。

通常、比較演算子と組み合わせて利用することで、より複雑な条件式を表現できます。

JavaScriptで利用可能な主な論理演算子

JavaScriptで利用可能な主な論理演算子は次の表の通りです。

【主な論理演算子】
演算子概要
&&左右の式が共にtrueの場合はtrue
50 === 50 && 100 === 100
//結果:true
||左右の式のどちらかがtrueの場合はtrue
50 === 50 || 100 === 500
//結果:true
!式がfalseの場合はtrue
!(50 > 100)
//結果:true

論理演算子の評価結果は、左式/右式の論理値によって異なってきます。

左式/右式の値と具体的な結果の対応関係は、次の表の通りです。

【論理演算子の結果】
左式右式&&||
truetruetruetrue
truefalsefalsetrue
falsetruefalsetrue
falsefalsefalsefalse

これらの対応関係をベン図(集合図)を使って表現すると、次のようになります。

ベン図,&&(AND),||(OR),|(NOT)

falsyな値とは

なお、JavaScriptでは、以下の値は暗黙的にfalseと見なされます。(これをfalsyな値と呼ぶこともあります。)

  • 空文字列(””)
  • 数値の0、NaN(Not a Number)
  • null、undefined

上記以外の値がすべてtrueと見なされます。

論理演算子のオペランドが必ずしも論理型のtruefalseである必要はありません。

非boolean型の場合、論理演算子は値をboolean型に変換したうえで判定するからです。

ショートカット演算とは

&&演算子や||演算子を利用する場合、「左式だけが評価されて、右式が評価されない」場合があります。

たとえば、||演算子であれば、左式がtrueであれば、右式がtruefalseであるとに関わらず、式全体はtrueとなります。

よって、左式がtrueである時点で、右式は評価されません。(&&演算子も同様で、左式がfalseの場合は右式は評価されません。)

これをショートカット演算(短絡演算)といいます。

ショートカット演算,論理積(&&)演算子,falseなら,true/falseに関係なく,条件式全体がfalse,評価されない,論理(||)演算子,trueなら,true/falseに関係なく,条件式全体がtrue,評価されない、右式はショートカット

ショートカット演算を利用したテクニック

ショートカット演算を利用することで、たとえば以下のようなコードが可能になります。

let name = '';
name = name || '横浜流星';
console.log(name);
// 結果:横浜流星

変数nameが、 空文字列(”)、数値の0、NaN、null、undefined(未定義)の場合(正確にはfalsyな値の場合)に、既定値として「横浜流星」をセットします。

ただし、空文字列(”)、数値の0などfalsyな値に意味がある場合には、上のイディオムは利用できません。

空文字列(”)、数値の0などが既定値(ここでは「横浜流星」)によって上書きされてしまうからです。

そのようなケースでは条件演算子を利用します。

name = (name === undefined ? '横浜流星' : name);

これによって、変数nameがundefined(未定義)である場合にのみ、変数nameに既定値「横浜流星」をセットします。

ショートカット演算を利用する場合の注意点

ただし、ショートカット演算の利用は、主にコードの意図が不明瞭になりやすいという点から、濫用すべきではありません。

たとえば以下のコードは、意味的に等価ですが、分岐の趣旨が不明瞭という意味で、後者はお勧めしません。

if(i === 1){
  console.log('変数iは1です。');
  //○  分岐の意図が明瞭
}

i === 1 && console.log('変数iは1です。');
//▵  分岐の意図が不明瞭

ビット演算子

ビット演算とは

ビット演算とは、整数値を2進数で表した場合の各桁に対して(ビット単位に)論理計算を行う演算のことをいいます。

JavaScriptで利用可能な主なビット演算子

JavaScriptで利用可能な主なビット演算子は次に掲げる表の通りです。

【主なビット演算子】
演算子概要
&左式と右式の両方にセットされているビット
10 & 13  1010 & 1101  1000  8
|左式と右式のどちらかにセットされているビット
10 | 13  1010 | 1101  1111  15
^左式と右式のどちらかにセットされていて、かつ、両方にセットされていないビット
10 ^ 13  1010 ^ 1101  0111  7
~ビットを反転
~10  ~1010  0101  -11
<<ビットを左にシフト
10 << 2  1010 << 2  101000  40
>>ビットを右にシフト(符号を維持)
10 >> 1  1010 >> 1  0101  5
>>>ビットを右にシフト、かつ、左端を0で埋める
10 >>> 2  1010 >>> 2  0010  2

ビット演算子は、さらにビット論理演算子ビットシフト演算子の2つに大別できます。

以下ではそれぞれの具体的な演算の流れを見ていくことにします。

ビット論理演算子を使ってビット演算をしてみよう

たとえば、以下はビット演算論理積による演算「10 & 13」の流れです。

10進数,2進数

与えられた整数を2進数で表したうえで、それぞれの桁について論理演算を行います。

論理積では以下のように行います。

  • 双方のビットが1(true)である場合 1
  • いずれかのビットが0(false)である場合 0

ビット演算子は、論理演算の結果、得られた2進数をもとに元の10進数表記で返します。

もっとも、このようなルールで行くと、次の否定演算子「~」の結果を不思議に思われる方もいるかもしれません。

10進数,2進数,否定

「与えられたビットを反転させた結果、「1010」は「0101」となるので、結果は5なのでは?」と思われる方もいるかもしれません。

しかし、結果が「-11」であるのは、「~」演算子では正負を表す符号も反転させているためです。

2進数で負数を表す場合、「ビット列を反転させて1を加えたものが、その絶対値になる」という規則があります。

つまり、ここでは「0101」を反転させた「1010」に1を加えた「1011」(10進数では11)がその絶対値となり、「0101」が「-11」となるわけです。

ビットシフト演算子を使ってビット演算をしてみよう

以下は、左ビットシフト演算子を使った演算の例です。

10進数,2進数,左ビットシフト演算子

ビットシフト演算でも、10進数を2進数と捉える点は同じです。

そして、その桁を指定された桁だけ、左または右にシフトします。

左シフトした場合、右側の桁は0で埋められます。

つまり、ここでは「1010」(10進数の10)が左シフトによって「101000」となるので、演算の結果はこれを10進数に変換した40になるわけです。

演算子の優先順位と結合則

式の中に複数の演算子が含まれている場合、「どのような順序で処理するか?」を判定する必要があります。

これを決めるのが、演算子の優先順位と結合則です。

特に複雑な式を記述する場合には、これを理解しておかないと、思わぬところで意図しない結果が発生することになるので、要注意です。

優先順位とは

数学でも「×」「÷」は「+」「-」よりも優先されます。

たとえば、「2 + 4 ÷ 2」の解が4であるのは、「2 + 4 ÷ 2 = 6 ÷ 2」ではなく「2 + 4 ÷ 2 = 2 + 2 = 4」であるからです。

これと同様に、JavaScriptでもそれぞれの演算子は優先順位を持っています。

1つの式の中に複数の演算子が含まれる場合、JavaScriptは優先順位を高い順に演算します。

【演算子の優先順位】
優先順位演算子
配列([])、かっこ(())
インクリメント(++)、デクリメント(--)、減算(-)、反転(~)、否定(!)
乗算(*)、除算(/)、余剰(%)
加算(+)、減算(-)、文字列連結(+)
シフト(<<、>>、<<<)
比較(<、<=、>=、>)
等価(==)、不等価(!=)、同値(===)、非同値(!==)
AND(&)
XOR(^)
OR(|)
論理積(&&)
論理和(||)
条件(?:)
代入(=)、複合代入(+=、-=など)
カンマ(,)

もっとも、実際にはこれだけある優先順位をきちんと記憶しておくことは困難です。

また、複雑な式になった場合に、「あとからコード読み解く際、どの順序で演算が行われているのかが一目で見て取れない」などの問題もあります。

そこで、複雑な式を記述する場合には、以下のようにできるだけ丸カッコを利用して、演算の優先順位を明示的に示しておくことをおすすめします。

3 * 5 + 2 * 2 (3 * 5) + (2 * 2)

もちろん、上のようなシンプルな式では丸カッコで明示する必要がないと思われるかもしれません。

しかし、これがもっと複雑な式になった場合には、コードの可読性が明らかに向上します。

結合則とは

結合則は、「演算子を左から右、右から左いずれの方向で結合するか」を決めるルールです。

演算子の優先順位が等しい場合、JavaScriptはこの結合則でもって、左右どちらから演算するのかを決定します。

【演算子の結合則】
結合性演算子の種類演算子
算術演算子+、-、*、/、%
比較演算子<、<=、>、>=、==、!=、===、!==
論理演算子&&
ビット演算子<<、>>、>>>、&、^、|
その他.、[]、()、,、instanceof、in
算術演算子++、--
代入演算子=、+=、-=、*=、/=、%=、&=、^=、|=
論理演算子!
ビット演算子~
条件演算子?:
その他-(符号反転)、+(無演算)、delete、typeof、void

たとえば、以下の式は意味的に等価です。

1 + 2 - 3 ⇔ (1 + 2) - 3

つまり、「+」「-」演算子は優先順位として等しく、かつ、左→右の結合則を持つので、左から順番に処理されていくというわけです。

一方、右→左の結合則を持つのは、主に代入演算子や単項/三項演算子などです。

たとえば、以下の式は双方とも同じ意味です。

z = x *= 3 ⇔ z = (x *= 3)

「=」「*=」演算子は優先順位として等しく、かつ、右→左の結合則を持つので、右から順番に評価されるというわけです。

この場合は、変数xを3倍した結果が変数zに格納されます。

演算子の優先順位と結合則


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です