Object.is reports whether two values are the same value. It is almost identical to === (strict equality), with exactly two deliberate differences: it calls NaN equal to NaN, and it calls +0 and -0 different. Those two cases are the only places where strict equality gives a result that disagrees with same-value equality, and handling them is the whole point of the method.
Implement objectIs(a, b). Return true when a and b are the same value and false otherwise, matching === in every case except the two above. Build it yourself — don't call the built-in Object.is.
function objectIs(a, b) {
// returns true if a and b are the "same value", else false
}
objectIs(1, 1); // true
objectIs('a', 'a'); // true
objectIs(1, '1'); // false — different types, just like ===
objectIs(NaN, NaN); // true — === would say false
objectIs(0, -0); // false — === would say true
objectIs(-0, -0); // true
objectIs({}, {}); // false — two different objects
const o = {};
objectIs(o, o); // true — the same reference
=== almost everywhere — the same primitive value is true, different types or values are false, two different objects are false, and the same reference is true.NaN equals NaN — the first difference from ===, which reports NaN === NaN as false.+0 and -0 differ — the second difference: objectIs(+0, -0) is false, even though +0 === -0 is true.== — this is same-value equality, never loose equality. No type coercion happens, so objectIs(0, false) and objectIs(null, undefined) are both false.null and undefined — each is the same value as itself, but not as the other.You'll write a two-value comparison that behaves like === in every case but the two where === surprises people: NaN and signed zero.
JavaScript already has ===, and it is almost what we want: it compares two values with no type coercion. But it has two quirks that come straight from the IEEE-754 floating-point standard. First, NaN === NaN is false — the "not a number" value is not even equal to itself. Second, +0 === -0 is true — the two zeros compare as equal, even though they are distinct values under the hood. Object.is is the fixed version: same-value equality, where NaN equals NaN and +0 is kept separate from -0. Everything else it answers exactly like ===.
Think of Object.is as === with two patches. Start from strict equality, then flip the answer in the only two spots where strict equality gives the "wrong" same-value result. The table below lines up a few pairs and shows where the two operators agree and where they part ways.
The most natural first version is to just hand back ===:
function objectIsNaive(a, b) {
return a === b;
}
This is correct for almost everything — equal primitives, different types, and object references all come out right. But it fails the two cases that are the entire reason Object.is exists. objectIsNaive(NaN, NaN) returns false when we need true, and objectIsNaive(+0, -0) returns true when we need false. So we need to special-case exactly those two and leave the rest to ===.
function objectIs(a, b) {
if (a === b) {
// a and b are strictly equal, so the only same-value mistake === can
// make here is calling +0 and -0 equal. For any non-zero value,
// `a !== 0` is true and we return true right away. When both are zero,
// 1 / +0 is Infinity but 1 / -0 is -Infinity, so comparing 1/a to 1/b
// is true only when the two zeros have the same sign.
return a !== 0 || 1 / a === 1 / b;
}
// a and b are NOT strictly equal. The only same-value pair === gets
// wrong in this direction is NaN and NaN. NaN is the one value that is
// never equal to itself, so `a !== a` is true only when a is NaN — and
// we return true only when BOTH are NaN.
return a !== a && b !== b;
}
module.exports = { objectIs };
The two branches map one-to-one onto the two quirks. When === says "equal," the only thing it might have gotten wrong is signed zero, so we double-check with 1 / a === 1 / b. Dividing by the two zeros is what makes their signs observable: 1 / +0 is Infinity, 1 / -0 is -Infinity, and those are plainly not equal. When === says "not equal," the only thing it might have gotten wrong is NaN, which we catch with a !== a — the self-inequality that no other value has.
Trace objectIs(NaN, NaN):
a === b? — NaN === NaN is false, so we skip the first branch entirely. This is exactly the quirk we are here to fix.a !== a && b !== b. NaN !== NaN is true, because NaN is never equal to itself, so both halves are true.true. We report the two NaNs as the same value, which is what Object.is promises.Now trace objectIs(0, -0):
a === b? — 0 === -0 is true, so we enter the first branch.a !== 0? — 0 !== 0 is false, so we do not return early; we fall through to the right side of the ||.1 / a === 1 / b? — 1 / 0 is Infinity, 1 / -0 is -Infinity, and Infinity === -Infinity is false.false. The two zeros are kept distinct, which is the other thing Object.is promises.== and calling it done — == is worse than === here, not better: it coerces types, so 0 == false and null == undefined are true. Same-value equality never coerces, so objectIs(0, false) is false.< or > — +0 < -0 is false and -0 < +0 is false too, so ordering can't tell them apart. Dividing to reach Infinity versus -Infinity is the standard trick.NaN with a === NaN — that is always false, so it never fires. Use a !== a (or Number.isNaN(a)), the self-inequality test.module.exports — the tests require('./objectIs'), so the export line has to be present or every test errors before it even runs.SameValueZero — there is a close cousin of same-value equality, used by Array.prototype.includes, Map keys, and Set values. It agrees with Object.is on NaN (both treat NaN as equal to NaN) but disagrees on zero: SameValueZero treats +0 and -0 as the same. That is why [+0].includes(-0) is true while objectIs(+0, -0) is false. Replacing the 1 / a === 1 / b check with a plain return true in the equal branch turns this function into SameValueZero.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Object.is reports whether two values are the same value. It is almost identical to === (strict equality), with exactly two deliberate differences: it calls NaN equal to NaN, and it calls +0 and -0 different. Those two cases are the only places where strict equality gives a result that disagrees with same-value equality, and handling them is the whole point of the method.
Implement objectIs(a, b). Return true when a and b are the same value and false otherwise, matching === in every case except the two above. Build it yourself — don't call the built-in Object.is.
function objectIs(a, b) {
// returns true if a and b are the "same value", else false
}
objectIs(1, 1); // true
objectIs('a', 'a'); // true
objectIs(1, '1'); // false — different types, just like ===
objectIs(NaN, NaN); // true — === would say false
objectIs(0, -0); // false — === would say true
objectIs(-0, -0); // true
objectIs({}, {}); // false — two different objects
const o = {};
objectIs(o, o); // true — the same reference
=== almost everywhere — the same primitive value is true, different types or values are false, two different objects are false, and the same reference is true.NaN equals NaN — the first difference from ===, which reports NaN === NaN as false.+0 and -0 differ — the second difference: objectIs(+0, -0) is false, even though +0 === -0 is true.== — this is same-value equality, never loose equality. No type coercion happens, so objectIs(0, false) and objectIs(null, undefined) are both false.null and undefined — each is the same value as itself, but not as the other.You'll write a two-value comparison that behaves like === in every case but the two where === surprises people: NaN and signed zero.
JavaScript already has ===, and it is almost what we want: it compares two values with no type coercion. But it has two quirks that come straight from the IEEE-754 floating-point standard. First, NaN === NaN is false — the "not a number" value is not even equal to itself. Second, +0 === -0 is true — the two zeros compare as equal, even though they are distinct values under the hood. Object.is is the fixed version: same-value equality, where NaN equals NaN and +0 is kept separate from -0. Everything else it answers exactly like ===.
Think of Object.is as === with two patches. Start from strict equality, then flip the answer in the only two spots where strict equality gives the "wrong" same-value result. The table below lines up a few pairs and shows where the two operators agree and where they part ways.
The most natural first version is to just hand back ===:
function objectIsNaive(a, b) {
return a === b;
}
This is correct for almost everything — equal primitives, different types, and object references all come out right. But it fails the two cases that are the entire reason Object.is exists. objectIsNaive(NaN, NaN) returns false when we need true, and objectIsNaive(+0, -0) returns true when we need false. So we need to special-case exactly those two and leave the rest to ===.
function objectIs(a, b) {
if (a === b) {
// a and b are strictly equal, so the only same-value mistake === can
// make here is calling +0 and -0 equal. For any non-zero value,
// `a !== 0` is true and we return true right away. When both are zero,
// 1 / +0 is Infinity but 1 / -0 is -Infinity, so comparing 1/a to 1/b
// is true only when the two zeros have the same sign.
return a !== 0 || 1 / a === 1 / b;
}
// a and b are NOT strictly equal. The only same-value pair === gets
// wrong in this direction is NaN and NaN. NaN is the one value that is
// never equal to itself, so `a !== a` is true only when a is NaN — and
// we return true only when BOTH are NaN.
return a !== a && b !== b;
}
module.exports = { objectIs };
The two branches map one-to-one onto the two quirks. When === says "equal," the only thing it might have gotten wrong is signed zero, so we double-check with 1 / a === 1 / b. Dividing by the two zeros is what makes their signs observable: 1 / +0 is Infinity, 1 / -0 is -Infinity, and those are plainly not equal. When === says "not equal," the only thing it might have gotten wrong is NaN, which we catch with a !== a — the self-inequality that no other value has.
Trace objectIs(NaN, NaN):
a === b? — NaN === NaN is false, so we skip the first branch entirely. This is exactly the quirk we are here to fix.a !== a && b !== b. NaN !== NaN is true, because NaN is never equal to itself, so both halves are true.true. We report the two NaNs as the same value, which is what Object.is promises.Now trace objectIs(0, -0):
a === b? — 0 === -0 is true, so we enter the first branch.a !== 0? — 0 !== 0 is false, so we do not return early; we fall through to the right side of the ||.1 / a === 1 / b? — 1 / 0 is Infinity, 1 / -0 is -Infinity, and Infinity === -Infinity is false.false. The two zeros are kept distinct, which is the other thing Object.is promises.== and calling it done — == is worse than === here, not better: it coerces types, so 0 == false and null == undefined are true. Same-value equality never coerces, so objectIs(0, false) is false.< or > — +0 < -0 is false and -0 < +0 is false too, so ordering can't tell them apart. Dividing to reach Infinity versus -Infinity is the standard trick.NaN with a === NaN — that is always false, so it never fires. Use a !== a (or Number.isNaN(a)), the self-inequality test.module.exports — the tests require('./objectIs'), so the export line has to be present or every test errors before it even runs.SameValueZero — there is a close cousin of same-value equality, used by Array.prototype.includes, Map keys, and Set values. It agrees with Object.is on NaN (both treat NaN as equal to NaN) but disagrees on zero: SameValueZero treats +0 and -0 as the same. That is why [+0].includes(-0) is true while objectIs(+0, -0) is false. Replacing the 1 / a === 1 / b check with a plain return true in the equal branch turns this function into SameValueZero.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.