Episode 1 — Fundamentals / 1.24 — Object Methods
1.24.e — Object.is()
In one sentence:
Object.is(value1, value2)performs a same-value equality check that fixes the two edge cases where===gives surprising results:NaNand signed zeros.
Navigation: <- 1.24.d — Object.fromEntries . 1.24.f — Object.keys ->
1. What does Object.is() do?
Object.is() compares two values and returns true if they are the same value. It behaves like === (strict equality) with two important corrections:
// Where === gets it "wrong" (per mathematical intuition):
NaN === NaN; // false -- surprising!
+0 === -0; // true -- hides sign information
// Object.is fixes both:
Object.is(NaN, NaN); // true -- NaN IS the same as NaN
Object.is(+0, -0); // false -- +0 and -0 ARE different values
2. Syntax
Object.is(value1, value2)
| Parameter | Description |
|---|---|
value1 | First value to compare |
value2 | Second value to compare |
| Returns | boolean — true if the values are the same value |
3. The three equality operators compared
Comprehensive comparison table
| Expression | == (loose) | === (strict) | Object.is() |
|---|---|---|---|
1, 1 | true | true | true |
1, "1" | true | false | false |
0, false | true | false | false |
null, undefined | true | false | false |
null, null | true | true | true |
undefined, undefined | true | true | true |
NaN, NaN | false | false | true |
+0, -0 | true | true | false |
+0, 0 | true | true | true |
"", false | true | false | false |
[], 0 | true | false | false |
{}, {} | false | false | false |
Summary of differences
| Operator | Type coercion? | NaN === NaN | +0 === -0 |
|---|---|---|---|
== (loose) | Yes | false | true |
=== (strict) | No | false | true |
Object.is() | No | true | false |
4. Understanding the NaN fix
In IEEE 754 floating-point math, NaN is defined as not equal to anything, including itself. This is the standard behavior that === follows. But logically, if you have two NaN values, they represent the same concept.
const a = NaN;
const b = NaN;
// === says they're different
console.log(a === b); // false
// Object.is says they're the same
console.log(Object.is(a, b)); // true
// Common NaN-producing operations
console.log(Object.is(0 / 0, NaN)); // true
console.log(Object.is(Math.sqrt(-1), NaN)); // true
console.log(Object.is(parseInt("abc"), NaN)); // true
console.log(Object.is(Number("hello"), NaN)); // true
Before Object.is: checking for NaN
// Old ways to check for NaN:
Number.isNaN(value); // recommended (ES6)
value !== value; // classic trick (only NaN is not equal to itself)
// Object.is approach:
Object.is(value, NaN); // also works
5. Understanding the signed-zero fix
JavaScript has both +0 and -0. They behave identically in almost all operations, but they are technically different values.
console.log(+0 === -0); // true -- === hides the difference
console.log(Object.is(+0, -0)); // false -- Object.is reveals it
// When does -0 appear?
console.log(-1 * 0); // -0
console.log(0 / -1); // -0
console.log(-0); // -0
// They look the same in most output
console.log(String(-0)); // "0"
console.log(-0 === 0); // true
console.log(-0 + ""); // "0"
// But they differ in:
console.log(1 / +0); // Infinity
console.log(1 / -0); // -Infinity
console.log(Object.is(1 / +0, 1 / -0)); // false (Infinity vs -Infinity)
When signed zeros matter
- Mathematical computations — direction of approach in limits
- Coordinate systems — distinguishing direction of movement along an axis
- Rarely in everyday web development
6. When to use Object.is()
Most code should use ===. Reach for Object.is() in specific scenarios:
Checking for NaN in comparisons
function updateIfChanged(oldVal, newVal) {
// === would say NaN !== NaN, causing unnecessary updates
if (!Object.is(oldVal, newVal)) {
console.log(`Changed from ${oldVal} to ${newVal}`);
}
}
updateIfChanged(NaN, NaN); // no log -- correctly sees them as equal
updateIfChanged(5, 5); // no log
updateIfChanged(5, 10); // "Changed from 5 to 10"
Building a generic comparison utility
function areSameValue(a, b) {
return Object.is(a, b);
}
// Handles all edge cases correctly
console.log(areSameValue(NaN, NaN)); // true
console.log(areSameValue(+0, -0)); // false
console.log(areSameValue(1, 1)); // true
console.log(areSameValue({}, {})); // false (different references)
Implementing a Set-like deduplication that handles NaN
function uniqueValues(arr) {
return arr.reduce((acc, val) => {
if (!acc.some(existing => Object.is(existing, val))) {
acc.push(val);
}
return acc;
}, []);
}
console.log(uniqueValues([1, NaN, 2, NaN, 3, 1]));
// [1, NaN, 2, 3] -- only one NaN kept
7. React uses Object.is internally
React's useState and useEffect use Object.is for state comparison to determine if a re-render is needed:
// React's internal comparison (simplified):
function stateHasChanged(oldState, newState) {
return !Object.is(oldState, newState);
}
// This is why:
const [count, setCount] = useState(0);
setCount(0); // No re-render! Object.is(0, 0) === true
// And why NaN state works:
const [val, setVal] = useState(NaN);
setVal(NaN); // No re-render! Object.is(NaN, NaN) === true
// (with === this would trigger infinite re-renders)
This is documented in React's source and is a practical reason to understand Object.is.
8. Polyfill
Understanding the polyfill helps solidify the concept:
// Polyfill (for understanding)
if (!Object.is) {
Object.is = function (x, y) {
if (x === y) {
// +0 !== -0 check:
// 1/+0 = Infinity, 1/-0 = -Infinity
return x !== 0 || 1 / x === 1 / y;
} else {
// NaN === NaN check:
// NaN is the only value not equal to itself
return x !== x && y !== y;
}
};
}
The polyfill reveals the two tricks:
- If
x === yis true butxis0, check if1/x === 1/y(catches+0vs-0) - If
x === yis false, check if both are NaN (x !== xis only true for NaN)
9. Object.is with objects and references
Object.is checks reference identity for objects, just like ===:
const obj = { a: 1 };
const same = obj;
const clone = { a: 1 };
Object.is(obj, same); // true -- same reference
Object.is(obj, clone); // false -- different objects, even with same content
Object.is([], []); // false
Object.is({}, {}); // false
It is not a deep equality check. For deep comparison, use libraries or write a recursive comparator.
10. Comparison with other NaN-checking methods
| Method | NaN detection | +0 vs -0 |
|---|---|---|
x === x (false means NaN) | Yes (inverted) | No distinction |
isNaN(x) (global) | Yes, but coerces first | N/A |
Number.isNaN(x) | Yes, no coercion | N/A |
Object.is(x, NaN) | Yes, no coercion | N/A |
Object.is(x, y) | Handles NaN equality | Distinguishes signs |
// Global isNaN coerces -- dangerous:
isNaN("hello"); // true! (coerces to NaN first)
Number.isNaN("hello"); // false (no coercion)
Object.is("hello", NaN); // false (no coercion)
11. Edge cases
// Symbols
const s = Symbol("id");
Object.is(s, s); // true
Object.is(Symbol("id"), Symbol("id")); // false (unique symbols)
// null and undefined
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(null, undefined); // false (unlike ==)
// Infinity
Object.is(Infinity, Infinity); // true
Object.is(-Infinity, -Infinity); // true
Object.is(Infinity, -Infinity); // false
// Booleans
Object.is(true, true); // true
Object.is(true, 1); // false (no coercion)
Key takeaways
Object.is(a, b)is like===butNaNequalsNaNand+0does not equal-0.- Most code uses
===—Object.isis for specific edge cases involving NaN or signed zeros. - React uses
Object.isinternally for state change detection. - For objects,
Object.ischecks reference identity, not deep equality. - The polyfill uses two tricks:
1/x === 1/yfor zero signs andx !== xfor NaN detection.
Explain-It Challenge
Explain without notes:
- What does
Object.is(NaN, NaN)return? How does this differ fromNaN === NaN? - Why would React choose
Object.isover===for state comparison? - Can
Object.istell you if two objects have the same contents? Why or why not?
Navigation: <- 1.24.d — Object.fromEntries . 1.24.f — Object.keys ->