You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: types-grammar/ch4.md
+109Lines changed: 109 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -222,6 +222,105 @@ ToNumber([]); // 0
222
222
223
223
By virtue of `ToPrimitive(..,"number")` delegation, these objects all have their default `valueOf()` method (inherited via `[[Prototype]]`) invoked.
224
224
225
+
### Equality Comparison
226
+
227
+
When JS needs to determine if two values are the *same value*, it invokes the `SameValue()`[^SameValue] operation, which delegates to a variety of related sub-operations.
228
+
229
+
This operation is very narrow and strict, and performs no coercion or any other special case exceptions. If two values are *exactly* the same, the result is `true`, otherwise it's `false`:
230
+
231
+
```
232
+
// SameValue() is abstract
233
+
234
+
SameValue("hello","\x68ello"); // true
235
+
SameValue("\u{1F4F1}","\uD83D\uDCF1"); // true
236
+
SameValue(42,42); // true
237
+
SameValue(NaN,NaN); // true
238
+
239
+
SameValue("\u00e9","\u0065\u0301"); // false
240
+
SameValue(0,-0); // false
241
+
SameValue([1,2,3],[1,2,3]); // false
242
+
```
243
+
244
+
A variation of these operations is `SameValueZero()` and its associated sub-operations. The main difference is that these operations treat `0` and `-0` as indistinguishable.
245
+
246
+
```
247
+
// SameValueZero() is abstract
248
+
249
+
SameValueZero(0,-0); // true
250
+
```
251
+
252
+
If the values are numeric (`number` or `bigint`), `SameValue()` and `SameValueZero()` both delegate to sub-operations of the same names, specialized for each `number` and `bigint` type, respectively.
253
+
254
+
Otherwise, `SameValueNonNumeric()` is the sub-operation delegated to if the values being compared are both non-numeric:
255
+
256
+
```
257
+
// SameValueNonNumeric() is abstract
258
+
259
+
SameValueNonNumeric("hello","hello"); // true
260
+
261
+
SameValueNonNumeric([1,2,3],[1,2,3]); // false
262
+
```
263
+
264
+
#### Higher-Abstracted Equality
265
+
266
+
Different from `SameValue()` and its variations, the specification also defines two important higher-abstraction abstract equality comparison operations:
267
+
268
+
*`IsStrictlyEqual()`[^StrictEquality]
269
+
*`IsLooselyEqual()`[^LooseEquality]
270
+
271
+
The `IsStrictlyEqual()` operation immediately returns `false` if the value-types being compared are different.
272
+
273
+
If the value-types are the same, `IsStrictlyEqual()` delegates to sub-operations for comparing `number` or `bigint` values. You might logically expect these delegated sub-operations to be the aforementioned numeric-specialized `SameValue()` / `SameValueZero()` operations. However, `IsStrictlyEqual()` instead delegates to `Number:equal()`[^NumberEqual] or `BigInt:equal()`[^BigIntEqual].
274
+
275
+
The difference between `Number:SameValue()` and `Number:equal()` is that the latter defines corner cases for `0` vs `-0` comparison:
276
+
277
+
```
278
+
// all of these are abstract operations
279
+
280
+
Number:SameValue(0,-0); // false
281
+
Number:SameValueZero(0,-0); // true
282
+
Number:equal(0,-0); // true
283
+
```
284
+
285
+
These operations also differ in `NaN` vs `NaN` comparison:
286
+
287
+
```
288
+
Number:SameValue(NaN,NaN); // true
289
+
Number:equal(NaN,NaN); // false
290
+
```
291
+
292
+
| WARNING: |
293
+
| :--- |
294
+
| So in other words, despite its name, `IsStrictlyEqual()` is not quite as "strict" as `SameValue()`, in that it *lies* when comparisons of `-0` or `NaN` are involved. |
295
+
296
+
The `IsLooselyEqual()` operation also inspects the value-types being compared; if they're the same, it immediately delegates to `IsStrictlyEqual()`.
297
+
298
+
But if the value-types being compared are different, `IsLooselyEqual()` performs a variety of *coercive equality* steps. It's important to note that this algorithm is always trying to reduce the comparison down to where both value-types are the same (and it tends to prefer `number` / `bigint`).
299
+
300
+
The steps of the *coercive equality* portion of the algorithm can roughly be summarized as follows:
301
+
302
+
1. If either value is `null` and the other is `undefined`, `IsLooselyEqual()` returns `true`. In other words, this algorithm applies *nullish* equality, in that `null` and `undefined` are coercively equal to each other (and to no other values).
303
+
304
+
2. If either value is a `number` and the other is a `string`, the `string` value is coerced to a `number` via `ToNumber()`.
305
+
306
+
3. If either value is a `bigint` and the other is a `string`, the `string` value is coerced to a `bigint` via `StringToBigInt()`.
307
+
308
+
4. If either value is a `boolean`, it's coerced to a `number`.
309
+
310
+
5. If either value is a non-primitive (object, etc), it's coerced to a primitive with `ToPrimitive()`; though a *hint* is not explicitly provided, the default behavior will be as if `"number"` was the hint.
311
+
312
+
Each time a coercion is performed in the above steps, the algorithm is *recursively* reinvoked with the new value(s). That process continues until the types are the same, and then the comparison is delegated to the `IsStrictlyEqual()` operation.
313
+
314
+
What can we take from this algorithm? First, we see there is a bias toward `number` (or `bigint`) comparison; it nevers coerce values to `string` or `boolean` value-types.
315
+
316
+
Importantly, we see that both `IsLooselyEqual()` and `IsStrictlyEqual()` are type-sensitive. `IsStrictlyEqual()` immediately bails if the types mismatch, whereas `IsLooselyEqual()` performs the extra work to coerce mismatching value-types to be the same value-types (again, ideally, `number` or `bigint`).
317
+
318
+
Moreover, if/once the types are the same, both operations are identical -- `IsLooselyEqual()` delegates to `IsStrictlyEqual()`.
319
+
320
+
### Relational Comparison
321
+
322
+
// TODO
323
+
225
324
[^AbstractOperations]: "7.1 Type Conversion", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-type-conversion ; Accessed August 2022
226
325
227
326
[^ToBoolean]: "7.1.2 ToBoolean(argument)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-toboolean ; Accessed August 2022
@@ -241,3 +340,13 @@ By virtue of `ToPrimitive(..,"number")` delegation, these objects all have their
241
340
[^NumberConstructor]: "21.1.1 The Number Constructor", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-number-constructor ; Accessed August 2022
242
341
243
342
[^NumberFunction]: "21.1.1.1 Number(value)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-number-constructor-number-value ; Accessed August 2022
343
+
344
+
[^SameValue]: "7.2.11 SameValue(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-samevalue ; Accessed August 2022
345
+
346
+
[^StrictEquality]: "7.2.16 IsStrictlyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-isstrictlyequal ; Accessed August 2022
347
+
348
+
[^LooseEquality]: "7.2.15 IsLooselyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-islooselyequal ; Accessed August 2022
349
+
350
+
[^NumberEqual]: "6.1.6.1.13 Number:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-number-equal ; Accessed August 2022
351
+
352
+
[^BigIntEqual]: "6.1.6.2.13 BigInt:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-bigint-equal ; Accessed August 2022
0 commit comments