Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor “Loose equality using ==” #4133

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 135 additions & 111 deletions files/en-us/web/javascript/equality_comparisons_and_sameness/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,133 +66,157 @@ <h2 id="Strict_equality_using">Strict equality using <code>===</code></h2>

<h2 id="Loose_equality_using">Loose equality using ==</h2>

<p>Loose equality compares two values for equality, <em>after</em> converting both values to a common type. After conversions (one or both sides may undergo conversions), the final equality comparison is performed exactly as <code>===</code> performs it. Loose equality is <em>symmetric</em>: <code>A == B</code> always has identical semantics to <code>B == A</code> for any values of <code>A</code> and <code>B</code> (except for the order of applied conversions).</p>
<p>The behavior for performing loose equality using <code>==</code> is as follows:</h2>

<p>The equality comparison is performed as follows for operands of the various types:</p>
<ul>
<li>Loose equality compares two values for equality <em>after</em> converting both values to a common type. After conversions (one or both sides may undergo conversions), the final equality comparison is performed exactly as <code>===</code> performs it.</li>

<table class="standard-table">
<thead>
<tr>
<th scope="row"></th>
<th colspan="8" scope="col" style="text-align: center;">Operand B</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"></th>
<td></td>
<td style="text-align: center;">Undefined</td>
<td style="text-align: center;">Null</td>
<td style="text-align: center;">Number</td>
<td style="text-align: center;">String</td>
<td style="text-align: center;">Boolean</td>
<td style="text-align: center;">Object</td>
<td style="text-align: center;">BigInt</td>
</tr>
<tr>
<th rowspan="7" scope="row">Operand A</th>
<td>Undefined</td>
<td style="text-align: center;"><code>true</code></td>
<td style="text-align: center;"><code>true</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
</tr>
<tr>
<td>Null</td>
<td style="text-align: center;"><code>true</code></td>
<td style="text-align: center;"><code>true</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
</tr>
<tr>
<td>Number</td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>A === B</code></td>
<td style="text-align: center;"><code>A === ToNumber(B)</code></td>
<td style="text-align: center;"><code>A === ToNumber(B)</code></td>
<td style="text-align: center;"><code>A == ToPrimitive(B)</code></td>
<td style="text-align: center;"><code>ℝ(A) = ℝ(B)</code></td>
</tr>
<tr>
<td>String</td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>ToNumber(A) === B</code></td>
<td style="text-align: center;"><code>A === B</code></td>
<td style="text-align: center;"><code>ToNumber(A) === ToNumber(B)</code></td>
<td style="text-align: center;"><code>A == ToPrimitive(B)</code></td>
<td style="text-align: center;"><code>StringToBigInt(A) === B</code></td>
</tr>
<tr>
<td>Boolean</td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>ToNumber(A) === B</code></td>
<td style="text-align: center;"><code>ToNumber(A) === ToNumber(B)</code></td>
<td style="text-align: center;"><code>A === B</code></td>
<td style="text-align: center;"><code>ToNumber(A) == ToPrimitive(B)</code></td>
<td style="text-align: center;"><code>ToNumber(A) == B</code></td>
</tr>
<tr>
<td>Object</td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>ToPrimitive(A) == B</code></td>
<td style="text-align: center;"><code>ToPrimitive(A) == B</code></td>
<td style="text-align: center;"><code>ToPrimitive(A) == ToNumber(B)</code></td>
<td style="text-align: center;"><code>A === B</code></td>
<td style="text-align: center;"><code>ToPrimitive(A) == B</code></td>
</tr>
<tr>
<td>BigInt</td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>false</code></td>
<td style="text-align: center;"><code>ℝ(A) = ℝ(B)</code></td>
<td style="text-align: center;"><code>A === StringToBigInt(B)</code></td>
<td style="text-align: center;"><code>A == ToNumber(B)</code></td>
<td style="text-align: center;"><code>A == ToPrimitive(B)</code></td>
<td style="text-align: center;"><code>A === B</code></td>
</tr>
</tbody>
</table>
<li>Loose equality is <em>symmetric</em>: <code>A == B</code> always has identical semantics to <code>B == A</code> for any values of <code>A</code> and <code>B</code> (except for the order of applied conversions).</li>

<li><code>undefined</code> and <code>null</code> are loosely equal; that is, <code>undefined&nbsp;==&nbsp;null</code> is true, and <code>null&nbsp;==&nbsp;undefined</code> is true</li>
</ul>

<p>Traditionally, and according to ECMAScript, all primitives and objects are loosely unequal to <code>undefined</code> and <code>null</code>. But most browsers permit a very narrow class of objects (specifically, the <code>document.all</code> object for any page), in some contexts, to act as if they <em>emulate</em> the value <code>undefined</code>. Loose equality is one such context: <code>null == A</code> and <code>undefined == A</code> evaluate to true if, and only if, A is an object that <em>emulates</em> <code>undefined</code>. In all other cases an object is never loosely equal to <code>undefined</code> or <code>null</code>.</p>

<p>Loose equality comparisons among other combinations of operand types are performed as shown in the tables below. The following notations are used in the tables:</p>

<p>In the above table:</p>
<ul>
<li><code>ToNumber(A)</code> attempts to convert its argument to a number before comparison. Its behavior is equivalent to <code>+A</code> (the unary + operator).</li>
<li><code>ToPrimitive(A)</code> attempts to convert its object argument to a primitive value, by invoking varying sequences of <code>A.toString</code> and <code>A.valueOf</code> methods on <code>A</code>.</li>
<li><code>ToPrimitive(A)</code> attempts to convert its object argument to a primitive value, by invoking varying sequences of <code>A.toString()</code> and <code>A.valueOf()</code> methods on <code>A</code>.</li>
<li><code>ℝ(A)</code> attempts to convert its argument to an ECMAScript <a href="https://tc39.es/ecma262/#mathematical-value">mathematical value</a>.</li>
<li><code>StringToBigInt(A)</code> attempts to convert its argument to a <code>BigInt</code> by applying the ECMAScript <a href="https://tc39.es/ecma262/#sec-stringtobigint"><code>StringToBigInt</code></a> algorithm.</li>
</ul>

<p>Traditionally, and according to ECMAScript, all objects are loosely unequal to <code>undefined</code> and <code>null</code>. But most browsers permit a very narrow class of objects (specifically, the <code>document.all</code> object for any page), in some contexts, to act as if they <em>emulate</em> the value <code>undefined</code>. Loose equality is one such context: <code>null == A</code> and <code>undefined == A</code> evaluate to true if, and only if, A is an object that <em>emulates</em> <code>undefined</code>. In all other cases an object is never loosely equal to <code>undefined</code> or <code>null</code>.</p>
<p><strong>number</strong> primitive <code>A</code> compared to operand <code>B</code>:</p>
<table class="standard-table">
<thead>
<tr>
<th>number</th>
<th>bigint</th>
<th>string</th>
<th>boolean</th>
<th>Object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>A&nbsp;===&nbsp;B</code></td>
<td><code>ℝ(A)&nbsp;equals&nbsp;ℝ(B)</code></td>
<td><code>A&nbsp;===&nbsp;ToNumber(B)</code></td>
<td><code>A&nbsp;===&nbsp;ToNumber(B)</code></td>
<td><code>A&nbsp;==&nbsp;ToPrimitive(B)</code></td>
</tr>
</tbody>
</table>

<pre class="brush: js">var num = 0;
var obj = new String('0');
var str = '0';
<p><strong>bigint</strong> primitive <code>A</code> compared to operand <code>B</code>:</p>
<table class="standard-table">
<thead>
<tr>
<th>number</th>
<th>bigint</th>
<th>string</th>
<th>boolean</th>
<th>Object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ℝ(A)&nbsp;equals&nbsp;ℝ(B)</code></td>
<td><code>A&nbsp;===&nbsp;B</code></td>
<td><code>A&nbsp;===&nbsp;StringToBigInt(B)</code></td>
<td><code>A&nbsp;==&nbsp;ToNumber(B)</code></td>
sideshowbarker marked this conversation as resolved.
Show resolved Hide resolved
<td><code>A&nbsp;==&nbsp;ToPrimitive(B)</code></td>
</tr>
</tbody>
</table>

console.log(num == num); // true
console.log(obj == obj); // true
console.log(str == str); // true
<p><strong>string</strong> primitive <code>A</code> compared to operand <code>B</code>:</p>
<table class="standard-table">
<thead>
<tr>
<th>number</th>
<th>bigint</th>
<th>string</th>
<th>boolean</th>
<th>Object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ToNumber(A)&nbsp;===&nbsp;B</code></td>
<td><code>StringToBigInt(A)&nbsp;===&nbsp;B</code></td>
<td><code>A&nbsp;===&nbsp;B</code></td>
<td><code>ToNumber(A)&nbsp;===&nbsp;ToNumber(B)</code></td>
<td><code>A&nbsp;==&nbsp;ToPrimitive(B)</code></td>
</tr>
</tbody>
</table>

console.log(num == obj); // true
console.log(num == str); // true
console.log(obj == str); // true
console.log(null == undefined); // true
<p><strong>boolean</strong> primitive <code>A</code> compared to operand <code>B</code>:</p>
<table class="standard-table">
<thead>
<tr>
<th>number</th>
<th>bigint</th>
<th>string</th>
<th>boolean</th>
<th>Object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ToNumber(A)&nbsp;===&nbsp;B</code></td>
<td><code>ToNumber(A)&nbsp;==&nbsp;B</code></td>
<td><code>ToNumber(A)&nbsp;===&nbsp;ToNumber(B)</code></td>
<td><code>A&nbsp;===&nbsp;B</code></td>
<td><code>ToNumber(A)&nbsp;==&nbsp;ToPrimitive(B)</code></td>
</tr>
</tbody>
</table>

// both false, except in rare cases
console.log(obj == null);
console.log(obj == undefined);
</pre>
<p><strong>Object</strong> <code>A</code> compared to operand <code>B</code>:</p>
<table class="standard-table">
<thead>
<tr>
<th>number</th>
<th>bigint</th>
<th>string</th>
<th>boolean</th>
<th>Object</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ToPrimitive(A)&nbsp;==&nbsp;B</code></td>
<td><code>ToPrimitive(A)&nbsp;==&nbsp;B</code></td>
<td><code>ToPrimitive(A)&nbsp;==&nbsp;B</code></td>
<td><code>ToPrimitive(A)&nbsp;==&nbsp;ToNumber(B)</code></td>
<td><code>A&nbsp;===&nbsp;B</code></td>
</tr>
</tbody>
</table>

<p>In most cases, using loose equality is discouraged. The result of a comparison using strict equality is easier to predict, and may evaluate more quickly due to the lack of type coercion.</p>

<h3 id="example">Example</h3>

<p>The following example demonstrates loose equality comparisons involving the number primitive <code>0</code>, the bigint primitive <code>0n</code>, the string primitive <code>'0'</code>, and an object whose <code>toString()</code> value is <code>'0'</code>.</p>

<pre class="brush: js">const num = 0;
const big = 0n;
const str = '0';
const obj = new String('0');

console.log(num == str); // true
console.log(big == num); // true
console.log(str == big); // true

console.log(num == obj); // true
console.log(big == obj); // true
console.log(str == obj); // true
</pre>

<h2 id="Same-value_equality">Same-value equality</h2>

<p>Same-value equality addresses a final use case: determining whether two values are <em>functionally identical</em> in all contexts. (This use case demonstrates an instance of the <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">Liskov substitution principle</a>.) One instance occurs when an attempt is made to mutate an immutable property:</p>
Expand Down