From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../equality_comparisons_and_sameness/index.html | 490 +++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 files/ja/web/javascript/equality_comparisons_and_sameness/index.html (limited to 'files/ja/web/javascript/equality_comparisons_and_sameness/index.html') diff --git a/files/ja/web/javascript/equality_comparisons_and_sameness/index.html b/files/ja/web/javascript/equality_comparisons_and_sameness/index.html new file mode 100644 index 0000000000..036d257be1 --- /dev/null +++ b/files/ja/web/javascript/equality_comparisons_and_sameness/index.html @@ -0,0 +1,490 @@ +--- +title: 等価性の比較と同一性 +slug: Web/JavaScript/Equality_comparisons_and_sameness +tags: + - Comparison + - Equality + - Intermediate + - JS + - JavaScript + - NaN + - SameValue + - SameValueZero + - Sameness + - 中級者 + - 同値 + - 比較 + - 等価性 +translation_of: Web/JavaScript/Equality_comparisons_and_sameness +--- +
{{jsSidebar("Intermediate")}}
+ +

ES2015 には、4 種類の等価性アルゴリズムがあります。

+ + + +

JavaScript には、3 種類の値比較演算子があります。

+ + + +

どの演算子を使用するかは、どのような比較を行いたいかに依存します。簡単に言えば、

+ + + +

なお、これらの区別はプリミティブ値の扱いについてのことです。いずれの場合も引数が概念的に似た構造を持つかどうかを比較する訳ではありません。プリミティブ値ではない x および y オブジェクトが同一の構造を持っていてもオブジェクトが異なれば、上記のいずれの形でも false と評価されます。

+ +

=== による厳格な等価性

+ +

厳格な等価性は、2 つの値が等しいか比較します。比較対象の値はどちらも、比較する前に別の値へ暗黙のうちに変換されることはありません。値が異なる型の場合、それらの値は等しくないとみなします。値が同じ型で数値ではない場合、同じ値であれば等しいとみなします。最後に、どちらの値も数値である場合、どちらも NaN ではなく同じ値である、あるいは一方が +0 かつもう一方が -0 であるときに等しいとみなします。

+ +
var num = 0;
+var obj = new String('0');
+var str = '0';
+
+console.log(num === num); // true
+console.log(obj === obj); // true
+console.log(str === str); // true
+
+console.log(num === obj); // false
+console.log(num === str); // false
+console.log(obj === str); // false
+console.log(null === undefined); // false
+console.log(obj === null); // false
+console.log(obj === undefined); // false
+
+ +

厳密な等価性は、たいていの使い方で正しい比較になります。数値以外のあらゆる値において、これは「値が自分自身と等しい」という明快な解釈を用います。数値においては、2つの極めて特殊なケースを扱えるようにわずかに異なる解釈を用います。1つ目は浮動小数点数の 0 には正と負の符号付きが存在することです。これは、ある種の数学的な解を表すために役立ちますが、ほとんどの場合は +0-0 の違いを意識せず、厳格な等価性ではこれらを同じ値として扱います。2つ目は浮動小数点数には非数 NaN の概念があることです、これは例えば正の無限大に負の無限大を加算するといった一定の数学的に明確ではない問題達の解を表します。厳格な等価性では NaN を他のどの値 (自分自身も含む) とも等しくないものとして扱います。((x !== x)true になる唯一の場合は xNaN である場合です。)

+ +

== による緩い等価性

+ +

緩い等価性は、両方の値を共通の型に変換した後で、2 つの値が等しいか比較します。(片方あるいは両方の変換が行われた) 変換処理後に、最終的な等価性の比較は === と全く同じ振る舞いです。緩い等価性は対称的であり、任意の値 A および B において、A == BB == A の意味は常に同じ意味です (変換処理を適用する順序を除く)。

+ +

等価性比較でさまざまな型のオペランドに対して以下のように振る舞います。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
オペランド B
UndefinedNullNumberStringBooleanObject
オペランド AUndefinedtruetruefalsefalsefalsefalse
Nulltruetruefalsefalsefalsefalse
NumberfalsefalseA === BA === ToNumber(B)A === ToNumber(B)A == ToPrimitive(B)
StringfalsefalseToNumber(A) === BA === BToNumber(A) === ToNumber(B)A == ToPrimitive(B)
BooleanfalsefalseToNumber(A) === BToNumber(A) === ToNumber(B)A === BToNumber(A) == ToPrimitive(B)
ObjectfalsefalseToPrimitive(A) == BToPrimitive(A) == BToPrimitive(A) == ToNumber(B)A === B
+ +

上の表で、ToNumber(A) は、比較前に引数を数値に変換しようとします。この振る舞いは +A (正の単項演算子) と同じです。ToPrimitive(A) は、A の持つ A.toString メソッド、そして A.valueOf メソッドの変換シーケンスを実施することで、オブジェクトの引数をプリミティブ値へ変換しようとを試みます。

+ +

伝統的にも、また ECMAScript によれば、すべてのオブジェクトは undefinednull に対して大雑把には等価でないとしています。しかし、ほとんどのブラウザーは、ごく一部のオブジェクト (特に、あらゆるページの document.all オブジェクト) が、いくつかの状況においては値 undefined のように振る舞うことを認めています。緩い等価性もそのようなものの一つです、A が undefined相当するオブジェクトである場合に限り、null == A および undefined == A は true になります。それ以外のどのオブジェクトも undefined および null と大雑把には等価とはなりません。

+ +
var num = 0;
+var obj = new String('0');
+var str = '0';
+
+console.log(num == num); // true
+console.log(obj == obj); // true
+console.log(str == str); // true
+
+console.log(num == obj); // true
+console.log(num == str); // true
+console.log(obj == str); // true
+console.log(null == undefined); // true
+
+// 特殊なケースを除き、どちらも false
+console.log(obj == null);
+console.log(obj == undefined);
+
+ +

緩い等価性を使用することは大体はよい考えでないと考える開発者もいます。厳格な等価性による比較の結果は予測が容易であり、評価が必要となる型強制がないためにより早くなります。

+ +

Same-value 等価性

+ +

最後に示す用法は Same-value 等価性です。これは、すべての状況で 2 つの値が機能的に同一かを判断します(この用法はリスコフの置換原則の実践例と言えます)。実例として、イミュータブルなプロパティを変化させようとした場合を見てみましょう:

+ +
// Number コンストラクタに immutable な NEGATIVE_ZERO プロパティを追加
+Object.defineProperty(Number, 'NEGATIVE_ZERO',
+                      { value: -0, writable: false, configurable: false, enumerable: false });
+
+function attemptMutation(v) {
+  Object.defineProperty(Number, 'NEGATIVE_ZERO', { value: v });
+}
+
+ +

イミュータブルなプロパティを変更しようとする操作が実際の変更を伴う場合、Object.defineProperty は例外を発生させます。しかし、実際の変更が伴わない場合は、Object.defineProperty は何もしません。v-0 であれば変更を要求されていないので、エラーは発生しません。しかし v+0 であれば、Number.NEGATIVE_ZERO のイミュータブルな値を変更しようとすることになります。内部的には、イミュータブルなプロパティが再定義された場合、新たに指定された値と現在の値が Same-value 等価性によって比較されます。

+ +

Same-value 等価性は {{jsxref("Object.is")}} メソッドによって提供されます。

+ +

Same-value-zero 等価性

+ +

Same-value 等価性に似ていますが、+0 と -0 は等しいとみなします。

+ +

仕様書における抽象的な等価性、厳格な等価性、Same value

+ +

ES5 では、== で実行する比較を Section 11.9.3, The Abstract Equality Algorithm で説明しています。また、=== の比較は 11.9.6, The Strict Equality Algorithm で説明しています (リンク先をご覧ください。簡単かつ読みやすくなっています。ヒント: 厳格な等価性のアルゴリズムを始めにご覧ください)。また ES5 の Section 9.12, The SameValue Algorithm では、JS エンジン内部で使用する SameValue について説明しています。大部分は厳格な等価性のアルゴリズムと同じですが、{{jsxref("Number")}} を扱う 11.9.6.4 および 9.12.4 が異なります。ES2015 では、このアルゴリズムを {{jsxref("Object.is")}} で公開するよう提案しています。

+ +

二重等号と三重等号について、11.9.6.1 で最初に行う型の確認を除けば、厳格な等価性のアルゴリズムは緩い等価性のアルゴリズムのサブセットと考えることができます。これは、11.9.6.2 から 7 が 11.9.3.1.a から f に対応するためです。

+ +

等価性の比較を理解するためのモデル?

+ +

ES2015 より前は、二重等号と三重等号について、一方は他方を "拡張した" ものであると聞いていたかもしれません。例えば、二重等号は三重等号と同じことをすべて行うだけでなくオペランドの型変換も行うことから、三重等号を拡張したものであると聞いたことがあるかもしれません。例えば、6 == "6" です (あるいは二重等号が基本形であり、三重等号は 2 つのオペランドが同一の型であることを要求するという制約を加えていることから、三重等号が拡張形であると言われたかもしれません。どちらが理解に適したモデルであるかは、どのような見方を選ぶかによって変わります)。

+ +

しかし内蔵の等価演算子に関するこの考え方は、その "連続体" に ES2015 の {{jsxref("Object.is")}} を含められるように広げることが可能なモデルではありません。{{jsxref("Object.is")}} は二重等号より単純に "緩い" のではなく、また三重等号より "厳格" でもなく、さらに両者の中間のどこにも置けません (すなわち二重等号より厳格でも、三重等号より緩くもありません)。同一性を比較した以下の表から、{{jsxref("Object.is")}} が {{jsxref("NaN")}} を扱う方法が原因であることがわかります。Object.is(NaN, NaN)false に評価されるのであれば、-0+0 を区別することにより、三重等号より厳格であることから寛容/厳格の連続体に含めることができることに注目してください。しかし {{jsxref("NaN")}} の扱いは、これが虚偽であることを表します。残念ながら、{{jsxref("Object.is")}} は等価演算子に関する寛容さや厳格さではなく、単純に固有の特性の観点から考えなければなりません。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
同一性の比較表
xy=====Object.isSameValueZero
undefinedundefinedtruetruetruetrue
nullnulltruetruetruetrue
truetruetruetruetruetrue
falsefalsetruetruetruetrue
'foo''foo'truetruetruetrue
00truetruetruetrue
+0-0truetruefalsetrue
+00truetruetruetrue
-00truetruefalsetrue
0falsetruefalsefalsefalse
""falsetruefalsefalsefalse
""0truefalsefalsefalse
'0'0truefalsefalsefalse
'17'17truefalsefalsefalse
[1, 2]'1,2'truefalsefalsefalse
new String('foo')'foo'truefalsefalsefalse
nullundefinedtruefalsefalsefalse
nullfalsefalsefalsefalsefalse
undefinedfalsefalsefalsefalsefalse
{ foo: 'bar' }{ foo: 'bar' }falsefalsefalsefalse
new String('foo')new String('foo')falsefalsefalsefalse
0nullfalsefalsefalsefalse
0NaNfalsefalsefalsefalse
'foo'NaNfalsefalsefalsefalse
NaNNaNfalsefalsetruetrue
+ +

{{jsxref("Object.is")}} と三重等号の使いどころ

+ +

一般的に、{{jsxref("Object.is")}} のゼロに対する特別な動作が関心の対象になりえると思われるのは、ある種のメタプログラミング方式に則る時、特にプロパティ記述子に関して {{jsxref("Object.defineProperty")}} の特徴の一部を再現したい時に限られます。このような要件が必要なければ、{{jsxref("Object.is")}} ではなく、代わりに === を使用してはいかがでしょう。2 つの {{jsxref("NaN")}} 値を比較した結果が true になることが必要な場合であっても、通常は、{{jsxref("NaN")}} をチェックして特別扱いする方が (前バージョンの ECMAScript からは {{jsxref("isNaN")}} メソッドを使えます) 、比較処理中に現れた全てのゼロについてその符号が周囲の処理からどう影響されるのか悩むよりも簡単です。

+ +

すべてを網羅してはいませんが、-0+0 の区別が発生する可能性がある内蔵メソッドや演算子を以下に示します。コード中ではこれらを考慮して下さい:

+ +
+
- (単項否定演算子)
+
+
let stoppingForce = obj.mass * -obj.velocity;
+ +

obj.velocity0 である (あるいは計算結果が 0 になる) とき、そこで -0 が生成されて stoppingForce に伝播します。

+
+
+ +
+
{{jsxref("Math.atan2")}}
+
{{jsxref("Math.ceil")}}
+
{{jsxref("Math.pow")}}
+
{{jsxref("Math.round")}}
+
引数に -0 が存在しなくても、場合によってはこれらのメソッドの戻り値として -0 が式に取り込まれる可能性があります。例えば、負の値の累乗で {{jsxref("Infinity", "-Infinity")}} が発生するように {{jsxref("Math.pow")}} を使用したとき、奇数の指数は -0 に評価されます。それぞれのメソッドのドキュメントを確認してください。
+
+ +
+
{{jsxref("Math.floor")}}
+
{{jsxref("Math.max")}}
+
{{jsxref("Math.min")}}
+
{{jsxref("Math.sin")}}
+
{{jsxref("Math.sqrt")}}
+
{{jsxref("Math.tan")}}
+
引数のひとつが -0 である場合に、これらのメソッドから -0 を戻り値として得る可能性があります。例えば、Math.min(-0, +0)-0 になります。それぞれのメソッドのドキュメントを確認してください。
+
+ +
+
~
+
<<
+
>>
+
これらの演算子は、内部で ToInt32 アルゴリズムを使用します。内部の 32 ビット整数型は 0 の表現が 1 種類しかないため、逆の演算を行った後に -0 は戻らないでしょう。例えば Object.is(~~(-0), -0)Object.is(-0 << 2 >> 2, -0) は、false になります。
+
+ +

ゼロの符号を考慮していない場合に、{{jsxref("Object.is")}} に頼ることは危険でしょう。もちろん -0+0 を区別する意図があれば、これはまさに望むことです。

+ +

注意: {{jsxref("Object.is")}} と NaN

+ +

{{jsxref("Object.is")}} の仕様書では、すべての {{jsxref("NaN")}} のインスタンスを同じオブジェクトとして扱っています。しかし、型付き配列が利用でき、インスタンスを区別することができるので、次の例のようにすべてのコンテキストで同じ動作をするとは限りません。

+ +
var f2b = x => new Uint8Array(new Float64Array([x]).buffer);
+var b2f = x => new Float64Array(x.buffer)[0];
+var n = f2b(NaN);
+n[0] = 1;
+var nan2 = b2f(n);
+nan2;
+// > NaN
+Object.is(nan2, NaN);
+// > true
+f2b(NaN);
+// > Uint8Array(8) [0, 0, 0, 0, 0, 0, 248,127)
+f2b(nan2);
+// > Uint8Array(8) [1, 0, 0, 0, 0, 0, 248,127)
+ +

関連情報

+ + -- cgit v1.2.3-54-g00ecf