第9章 演算子
目次
本書のご購入
(広告、ブロックしないでください。)

第9章 演算子

本章では演算子の概要を説明します。

演算子とオブジェクト

すべての演算子は型強制で説明されているように)オペランドを適切な型に強制変換します。ほとんどの演算子はプリミティブ値(算術演算子や比較演算子など)でのみ機能します。つまり、オブジェクトは処理される前にプリミティブに変換されます。これが不都合な例として、多くの言語で配列の連結に使用されているプラス演算子が挙げられます。しかし、JavaScriptではこの演算子は配列を文字列に変換して連結します。

> [1, 2] + [3]
'1,23'
> String([1, 2])
'1,2'
> String([3])
'3'

注記

JavaScriptでは、等価演算子でさえ、演算子をオーバーロードしたり、カスタマイズしたりすることはできません。

代入演算子

単純な代入演算子を使用する方法はいくつかあります。

x = value
前に宣言された変数xに代入します。
var x = value
変数の宣言と代入を組み合わせます。
obj.propKey = value
プロパティを設定します。
obj['propKey'] = value
プロパティを設定します。
arr[index] = value
配列要素を設定します[10]

代入は、代入された値を評価する式です。これにより、代入を連鎖させることができます。たとえば、次のステートメントは0yxの両方に代入します。

x = y = 0;

複合代入演算子

複合代入演算子op=のように記述され、ここでopはいくつかの二項演算子の1つであり、=は代入演算子です。次の2つの式は同等です。

myvar op= value
myvar = myvar op value

言い換えると、複合代入演算子op=は両方のオペランドにopを適用し、結果を最初のオペランドに代入します。複合代入を使用してプラス演算子(+)を使用する例を見てみましょう。

> var x = 2;
> x += 3
5
> x
5

以下はすべて複合代入演算子です。

等価演算子:=== と ==

JavaScriptには、2つの値が等しいかどうかを判断する2つの方法があります。

  • 厳密な等価性(===)と厳密な不等価性(!==)は同じ型を持つ値のみを等しいとみなします。
  • 通常の(または「寛大な」)等価性(==)と不等価性(!=)は、厳密な(不)等価性と同様に、比較する前に異なる型の値を変換しようとします。

寛大な等価性は、2つの点で問題があります。まず、変換の実行方法がわかりにくいことです。第二に、演算子が非常に寛容であるため、型エラーが長く隠れたままになる可能性があります。

常に厳密な等価性を使用し、寛大な等価性は避けてください。後者について学ぶ必要があるのは、なぜ避けるべきかを知りたい場合のみです。

等価性はカスタマイズできません。JavaScriptでは演算子をオーバーロードすることはできず、等価性の動作をカスタマイズすることもできません。比較に影響を与える必要がある操作がいくつかあります。たとえば、Array.prototype.sort()要素のソートと反転(破壊的)参照)です。このメソッドは、配列要素間のすべての比較を実行するコールバックをオプションで受け入れます。

厳密な等価性(===、!==)

異なる型の値は、厳密には決して等しくありません。両方の値が同じ型を持つ場合、次のアサーションが成り立ちます。

落とし穴:NaN

特殊な数値NaNNaN参照)は自身と等しくありません。

> NaN === NaN
false

したがって、落とし穴:値がNaNかどうかを確認するで説明されている他の方法を使用して確認する必要があります。

厳密な不等価性(!==)

厳密な不等価性比較:

x !== y

厳密な等価性比較の否定と同等です。

!(x === y)

通常の(寛大な)等価性(==、!=)

通常の等価性による比較のアルゴリズムは、次のとおりです。両方のオペランドが同じ型(6つの仕様型のいずれか—Undefined、Null、Boolean、Number、String、およびObject)を持つ場合、厳密な等価性を使用して比較します。

そうでない場合、オペランドが

  1. undefinednullの場合、それらは寛大に等しいとみなされます。

    > undefined == null
    true
  2. 文字列と数値の場合、文字列を数値に変換し、両方のオペランドを厳密な等価性を使用して比較します。
  3. ブール値と非ブール値の場合、ブール値を数値に変換し、寛大に(再び)比較します。
  4. オブジェクトと数値または文字列の場合、オブジェクトをプリミティブに変換しようとします(アルゴリズム:ToPrimitive()—値をプリミティブに変換するで説明されているアルゴリズムを使用)し、寛大に(再び)比較します。

そうでない場合(上記のいずれのケースも適用されない場合)、寛大な比較の結果はfalseになります。

寛大な不等価性(!=)

不等価性比較:

x != y

等価性比較の否定と同等です。

!(x == y)

落とし穴:寛大な等価性はブール値への変換とは異なります。

ステップ3は、等価性とブール値への変換ブール値への変換参照)が異なる方法で機能することを意味します。ブール値に変換した場合、1より大きい数値はtrueになります(たとえば、ifステートメント内)。しかし、これらの数値はtrueと寛大には等しくありません。コメントは、結果がどのように計算されたかを説明しています。

> 2 == true  // 2 === 1
false
> 2 == false  // 2 === 0
false

> 1 == true  // 1 === 1
true
> 0 == false  // 0 === 0
true

同様に、空の文字列はfalseと等しいですが、すべての空でない文字列がtrueと等しいわけではありません。

> '' == false   // 0 === 0
true
> '1' == true  // 1 === 1
true
> '2' == true  // 2 === 1
false
> 'abc' == true  // NaN === 1
false

落とし穴:寛大な等価性と文字列

寛大さの一部は、目的によって役立つ場合があります。

> 'abc' == new String('abc')  // 'abc' == 'abc'
true
> '123' == 123  // 123 === 123
true

その他のケースは、JavaScriptが文字列を数値に変換する方法(数値への変換参照)のために問題があります。

> '\n\t123\r ' == 123  // usually not OK
true
> '' == 0  // 0 === 0
true

落とし穴:寛大な等価性とオブジェクト

オブジェクトと非オブジェクトを比較する場合、プリミティブに変換され、奇妙な結果になります。

> {} == '[object Object]'
true
> ['123'] == 123
true
> [] == 0
true

ただし、2つのオブジェクトは、同じオブジェクトである場合にのみ等しくなります。つまり、2つのラッパーオブジェクトを実際に比較することはできません。

> new Boolean(true) === new Boolean(true)
false
> new Number(123) === new Number(123)
false
> new String('abc') == new String('abc')
false

==には有効なユースケースがありません

寛大な等価性(==)の有効なユースケースについて読むことがあります。このセクションでは、それらをリストし、より良い代替案を示します。

ユースケース:undefinedまたはnullの確認

次の比較は、xundefinedでもnullでもないことを保証します。

if (x != null) ...

これはこのチェックを記述するコンパクトな方法ですが、初心者を混乱させ、専門家でもそれがタイプミスかどうかを確実に判断できません。したがって、xに値があるかどうかを確認する場合は、真偽値の標準チェック(真偽値で説明)を使用してください。

if (x) ...

より正確にするには、両方の値に対して明示的なチェックを実行する必要があります。

if (x !== undefined && x !== null) ...

ユースケース:文字列の数値の処理

xが数値であるか、数値を文字列として表したものかどうかがわからない場合、次のようなチェックを使用できます。

if (x == 123) ...

前述のチェックは、x123または'123'のいずれかであるかどうかを確認します。これも非常にコンパクトですが、明示的な方が優れています。

if (Number(x) === 123) ...

ユースケース:ラッパーインスタンスとプリミティブの比較

寛大な等価性により、プリミティブとラップされたプリミティブを比較できます。

> 'abc' == new String('abc')
true

このアプローチには3つの理由があります。まず、寛大な等価性は、ラップされたプリミティブ間では機能しません。

> new String('abc') == new String('abc')
false

第二に、とにかくラッパーは避けるべきです。第三に、ラッパーを使用する場合、明示的な方が優れています。

if (wrapped.valueOf() === 'abc') ...

順序演算子

JavaScriptは次の順序演算子を知っています。

  • より小さい(<)
  • より小さいか等しい(<=)
  • より大きい(>)
  • より大きいか等しい(>=)

これらの演算子は数値と文字列で機能します。

> 7 >= 5
true
> 'apple' < 'orange'
true

文字列の場合、大文字と小文字が区別され、アクセントなどの機能を適切に処理しないため、あまり役に立ちません(詳細については、文字列の比較を参照)。

アルゴリズム

比較を評価するには、

x < y

次の手順を実行します。

  1. 両方のオペランドがプリミティブであることを確認します。オブジェクトobjは、内部操作ToPrimitive(obj, Number)アルゴリズム:ToPrimitive()—値をプリミティブに変換するを参照)を使用してプリミティブに変換され、これによりobj.valueOf()obj.toString()が呼び出されます。
  2. 両方のオペランドが文字列の場合、文字列のJavaScript文字を表す16ビットコードユニット(第24章参照)を辞書式に比較して比較します。
  3. そうでない場合、両方のオペランドを数値に変換して数値で比較します。

他の順序演算子も同様に処理されます。

プラス演算子(+)

おおよそ、プラス演算子はオペランドを調べます。一方のオペランドが文字列の場合、もう一方も文字列に変換され、両方が連結されます。

> 'foo' + 3
'foo3'
> 3 + 'foo'
'3foo'

> 'Colors: ' + [ 'red', 'green', 'blue' ]
'Colors: red,green,blue'

それ以外の場合、両方のオペランドは数値に変換され(数値への変換を参照)、加算されます。

> 3 + 1
4
> 3 + true
4

つまり、評価の順序が重要になります。

> 'foo' + (1 + 2)
'foo3'
> ('foo' + 1) + 2
'foo12'

アルゴリズム

加算は次のように評価します。

value1 + value2

次の手順を実行します。

  1. 両方のオペランドがプリミティブであることを確認します。オブジェクトobjは、内部操作ToPrimitive(obj)アルゴリズム:ToPrimitive()—値をプリミティブに変換するを参照)を介してプリミティブに変換され、これにはobj.valueOf()と、場合によってはobj.toString()が呼び出されます。日付の場合、最初にobj.toString()が呼び出されます。
  2. いずれかのオペランドが文字列の場合、両方を文字列に変換し、結果の連結を返します。
  3. それ以外の場合、両方のオペランドを数値に変換し、結果の合計を返します。

ブール値と数値の演算子

次の演算子は、単一タイプのオペランドのみを持ち、そのタイプの結果も生成します。これについては、別の場所で説明します。

ブール演算子

数値演算子

特殊な演算子

ここでは、条件演算子、コンマ演算子、およびvoid演算子という特殊な演算子について説明します。

条件演算子(? :)

条件演算子は、式です:

«condition» ? «if_true» : «if_false»

条件がtrueの場合、結果はif_trueになります。それ以外の場合は、結果はif_falseになります。例:

var x = (obj ? obj.prop : null);

演算子の周りの括弧は必要ありませんが、読みやすくなります。

コンマ演算子

«left» , «right»

コンマ演算子は、両方のオペランドを評価し、rightの結果を返します。おおよそ、式に対してセミコロンが文に対して行うことを行います。

この例は、2番目のオペランドが演算子の結果になることを示しています。

> 123, 'abc'
'abc'

この例は、両方のオペランドが評価されることを示しています。

> var x = 0;
> var y = (x++, 10);

> x
1
> y
10

コンマ演算子は分かりにくいものです。賢くならず、できる限り2つの個別の文を書く方が良いでしょう。

void演算子

void演算子の構文は次のとおりです。

void «expr»

exprを評価し、undefinedを返します。いくつかの例を以下に示します。

> void 0
undefined
> void (0)
undefined

> void 4+7  // same as (void 4)+7
NaN
> void (4+7)
undefined

> var x;
> x = 3
3
> void (x = 5)
undefined
> x
5

したがって、voidを関数として実装する場合、次のようになります。

function myVoid(expr) {
    return undefined;
}

void演算子はオペランドに密接に関連付けられているため、必要に応じて括弧を使用します。たとえば、void 4+7(void 4)+7としてバインドされます。

voidは何に使用されますか?

ECMAScript 5では、voidはほとんど役に立ちません。主な使用例は次のとおりです。

JavaScriptにvoid演算子があるのはなぜですか?

JavaScriptの作成者であるBrendan Eichによると、彼はjavascript:リンク(前述の使用例の1つ)を支援するために言語に追加しました。

javascript: URLで定義されていない以外の値を簡単に破棄できるように、Netscape 2の出荷前にvoid演算子をJSに追加しました。[12]

typeofとinstanceofによる値の分類

値を分類したい場合、残念ながらJavaScriptではプリミティブとオブジェクトを区別する必要があります(第8章を参照)。

  • typeof演算子は、プリミティブとオブジェクトを区別し、プリミティブの型を決定します。
  • instanceof演算子は、オブジェクトが特定のコンストラクタのインスタンスかどうかを決定します。JavaScriptにおけるオブジェクト指向プログラミングの詳細については、第17章を参照してください。

typeof:プリミティブの分類

typeof演算子:

typeof «value»

valueがどのような種類の値であるかを記述する文字列を返します。いくつかの例を以下に示します。

> typeof undefined
'undefined'
> typeof 'abc'
'string'
> typeof {}
'object'
> typeof []
'object'

typeofは、プリミティブとオブジェクトを区別し、プリミティブを分類するために使用されます(instanceofでは処理できません)。残念ながら、この演算子の結果は完全に論理的ではなく、ECMAScript仕様の型(JavaScriptの型で説明されています)とは大まかにしか対応していません。

オペランド結果

undefined、宣言されていない変数

'undefined'

null

'object'

ブール値

'boolean'

数値

'number'

文字列

'string'

関数

'function'

その他すべての通常の値

'object'

(エンジンによって作成された値)

JavaScriptエンジンは、typeofが任意の文字列(この表に記載されているすべての結果とは異なる)を返す値を作成できます。

落とし穴:typeof null

残念ながら、typeof null'object'です。これはバグと見なされています(nullは内部型Objectのメンバーではありません)が、既存のコードを壊してしまうため、修正できません。nullには注意する必要があります。たとえば、次の関数はvalueがオブジェクトかどうかをチェックします。

function isObject(value) {
    return (value !== null
       && (typeof value === 'object'
           || typeof value === 'function'));
}

試してみる

> isObject(123)
false
> isObject(null)
false
> isObject({})
true

typeof nullの歴史

最初のJavaScriptエンジンは、JavaScriptの値を32ビットワードとして表していました。そのようなワードの下位3ビットは型タグとして使用され、値がオブジェクト、整数、倍精度浮動小数点数、文字列、またはブール値かどうかを示していました(ご覧のとおり、この初期のエンジンですら、可能であれば数値を整数として格納していました)。

オブジェクトの型タグは000でした。nullの値を表すために、エンジンはマシン語のNULLポインタ、つまりすべてのビットがゼロのワードを使用していました。typeofは型タグをチェックして値の型を決定したため、nullをオブジェクトとして報告しました。[13]

変数の存在をチェックする

チェック:

typeof x === 'undefined'

には2つの使用例があります。

  1. xundefinedかどうかを判断します。
  2. 変数xが存在するかどうかを判断します。

両方の使用例の例を以下に示します。

> var foo;
> typeof foo === 'undefined'
true

> typeof undeclaredVariable === 'undefined'
true

最初の使用例では、undefinedと直接比較する方が通常は良い選択肢です。ただし、2番目の使用例では機能しません。

> var foo;
> foo === undefined
true

> undeclaredVariable === undefined
ReferenceError: undeclaredVariable is not defined

instanceof:オブジェクトが特定のコンストラクタのインスタンスかどうかをチェックする

instanceof演算子

«value» instanceof «Constr»

valueがコンストラクタConstrまたはサブコンストラクタによって作成されたかどうかを決定します。いくつかの例を以下に示します。

> {} instanceof Object
true
> [] instanceof Array  // constructor of []
true
> [] instanceof Object  // super-constructor of []
true

予想通り、instanceofは、非値undefinednullではfalseです。

> undefined instanceof Object
false
> null instanceof Object
false

しかし、他のすべてのプリミティブ値でもfalseです。

> 'abc' instanceof Object
false
> 123 instanceof Object
false

instanceofの詳細については、instanceof演算子を参照してください。



[10] 厳密に言えば、配列要素の設定はプロパティの設定の特殊ケースです。

[11] IIFEにvoidを使うことを教えてくれたBrandon Benvie (@benvie)に感謝します。

[13] 最初のJavaScriptエンジンのソースコードを教えてくれたTom Schuster (@evilpies)に感謝します。

次:10. ブーリアン