4

EDIT: Based on everyone's feedback, the original version of this question is more design-related, not standards-related. Making more SO-friendly.


Original:

Should a JS primitive be considered "equivalent" to an object-wrapped version of that primitive according to the ECMA standards?


Revised Question

Is there a universal agreement on how to compare primitive-wrapped objects in current JavaScript?

var n = new Number(1),
    p = 1;
n === p;     // false
typeof n;    // "object"
typeof p;    // "number"
+n === p;    // true, but you need coercion.

EDIT:

As @Pointy commented, ECMA spec (262, S15.1.2.4) describes a Number.isNaN() method that behaves as follows:

Number.isNaN(NaN);                // true
Number.isNaN(new Number(NaN));    // false
Number.isNaN(+(new Number(NaN))); // true, but you need coercion.

Apparently, the justification for this behavior is that isNaN will return true IF the argument coerces to NaN. new Number(NaN) does not directly coerce based on how the native isNaN operates.

It seems that the performance hit and trickiness in type conversion, etc, of directly using native Object Wrappers as opposed to primitives outweighs the semantic benefits for now.

See this JSPerf.

  • It seems like you've already demonstrated that primitive numbers and Object Numbers are equivalent in value but not in type; what's your question? – Evan Davis Jan 02 '13 at 21:04
  • My question is that should they be considered equivalent if they are equivalent in value, but not in type? – sjhcockrell Jan 02 '13 at 21:06
  • 1
    Why are you writing such a function? JavaScript already gives you `==` and `===` ... – Pointy Jan 02 '13 at 21:09
  • @Pointy It might be useful for objects. IE: `{one:1} == {one:1}` returns false. – Shmiddty Jan 02 '13 at 21:12
  • Well, the standard effectively says they're equal in the `==` sense, but not equal in the `===` sense. What definition of "equal" are you looking for exactly? – pimvdb Jan 02 '13 at 21:14
  • @Pointy Semantics. If you want to tell whether [1,2] and [1,2] are equivalent in value, === will always return false. While they are equivalent in value, structure, and type, they are not strictly equivalent by current ECMA standards. – sjhcockrell Jan 02 '13 at 21:15
  • 1
    @Mathletics "id est" not "Internet Explorer" :P – Shmiddty Jan 02 '13 at 21:21
  • @sjhcockrell yes but that's what `==` is for. – Pointy Jan 02 '13 at 21:22
  • @Shmiddty LOL, whoops! browsers on the brain! – Evan Davis Jan 02 '13 at 21:22
  • @Pointy `​console.log([1,2]==[1,2], {one:1}=={one:1});` logs `false false` – Shmiddty Jan 02 '13 at 21:26
  • It sounds like you're making up *your own* definition of "equal", where `[1, 2]` equals `[1, 2]`. In that case, you should probably also decide for yourself whether `1` and `new Number(1)` should be considered equal. If you want to use an implementation that follows the *specification* exactly, then use `==` or `===`. – pimvdb Jan 02 '13 at 21:28
  • @Pointy I think `{one:1} == {one:1} // false` is by design, because each object is allocated to a different spot in memory(?) and are considered two different objects and therefore not equivalent. If it were `a = b = {one: 1}` then `a == b // true`. – sjhcockrell Jan 02 '13 at 21:29
  • @pimvdb I think you're right. So this may be a design question rather than a practice question, in which case my question isn't suitable for SO and someone can close it. :) Cheers. – sjhcockrell Jan 02 '13 at 21:30
  • Yeah, I'm afraid this isn't really answerable. In fact, you can also make your own objects with a closure. Then the values of two objects can be seemingly equal, but calling a function on those objects would return different values. In that case it's even more difficult to determine whether such objects can be equal. – pimvdb Jan 02 '13 at 21:41
  • Rather than kill of this thread completely, perhaps it would make more sense to just limit it's scope a bit? The question of "is there a universal agreement on how to compare primitive-wrapped objects?" is a legit SO one, and it has an answer (no). That question I could see being of value/interest to other future SO readers. – machineghost Jan 02 '13 at 21:44
  • @Shmiddty of course, because JavaScript doesn't do deep comparison (which is an extremely hard thing to do properly in the general case; maybe not possible at all in any useful way due to the nuances of what "equal" should mean for complicated structured types). – Pointy Jan 02 '13 at 21:47
  • I think it could be useful--I'm pretty new to SO, so I'll take a crack at constraining the question above. – sjhcockrell Jan 02 '13 at 21:47
  • @Pointy (and Schmiddty) Right. Part of it is that I was trying to design this particular isEquals() fn to approximate deep comparison, and provide a better-performing alternative to Underscore.isEquals(). [See this JSPerf](http://jsperf.com/belt-vs-underscore-isequal/13) – sjhcockrell Jan 02 '13 at 22:04

1 Answers1

2

The short answer to your question is no, there is no consensus on how to compare values in JS because the question is too situational; it depends heavily on your particular circumstances.

But, to give some advice/provide a longer answer .... object versions of primitives are evil (in the "they will cause you lots of bugs sense", not in a moral sense), and should be avoided if possible. Therefore, unless you have a compelling reason to handle both, I'd suggest that you do not account for object-wrapped primitives, and simply stick to un-wrapped primitives in your code.

Plus, if you don't account for wrapped primitives it should eliminate any need for you to even have an equals method in the first place.

* Edit *

Just saw your latest comment, and if you need to compare arrays then the built-in == and === won't cut it. Even so, I'd recommend making an arrayEquals method rather than just an equals method, as you'll avoid lots of drama by keeping your function as focused as possible and using the built-in JS comparators as much as possible.

And if you do wrap that in some sort of general function, for convenience:

function equals(left, right) {
    if (left.slice && right.slice) { // lame array check
        return arrayEquals(left, right);
    }
    return left == right;
}

I'd still recommend against handling primitive-wrapped objects, unless by "handle" you make your function throw an error if it's passed a primitive-wrapped object. Again, because these objects will only cause you trouble, you should strive to avoid them as much as possible, and not leave yourself openings to introduce bad code.

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • I agree with staying away with the primitive-wrapped objects--they're currently much slower and more confusing. I'm building some library code and have gotten feedback that the way I've handled it (`isEqual( 1, new Number(1)`) // returns false, because `1 === new Number(1)` returns false) is "wrong." If there's no standardized way to handle Obj-wrapper/primitive equivalence out there, my question might be more of a "library design question" of the type that SO tries to avoid. – sjhcockrell Jan 02 '13 at 21:22
  • Yeah, I think that's it. There is no universal consensus (except maybe that primitive-wrapped objects are a pain), so it ultimately comes down to what makes sense for your case. I tried to explain some principles (like trying not to "re-invent the wheel" as much as possible) that should be pretty universal, but beyond them you just get in to the kind of subjective discussion that, as you noted, is frowned upon here (not to mention pointless: what is "right" is whatever is "right" for your case). – machineghost Jan 02 '13 at 21:31
  • @sjhcockrell Take note of the fact that the (relatively new) `Number.isNaN()` API returns `false` if you pass it the boxed version of `NaN` - that is, `Number.isNaN(new Number(NaN))` is `false`. – Pointy Jan 02 '13 at 21:37
  • @Pointy That's a supremely useful thing to know--based on 15.1.2.4 in the ECMA-262 spec, it's because only NaN can be coerced to NaN. Thanks for bringing that up; I'll add that to the question above. – sjhcockrell Jan 02 '13 at 21:46