Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

50 === 50: false. 50 == 50: true?

Tags:

javascript

I'm at a total loss.

I have a function..

Number.prototype.abs = function () {
    return this >= 0 ? this : this * -1;
};

..that returns the absolute value of a number..

(50).abs(); // 50
(-50).abs(); // 50

..but that doesn't compare correctly..

(50).abs() === 50; // False

..sometimes.

(50).abs() == 50; // True
(-50).abs() === 50; // True

The thing about it, is that it works in Chrome 12, and Firefox 4, but not in IE 9, Safari 5, or Opera 11.

I don't see anything wrong with the code, and since it works in Chrome and Firefox, it's something browser specific but I don't know what.

Update: The browser specific difference is strict mode support. I run my code in strict mode, which introduces some changes that make my code work. The reason it failed in the browsers it did, is because they have an incomplete or missing strict mode.

Why is it returning false?

like image 313
McKayla Avatar asked May 18 '11 02:05

McKayla


4 Answers

Even though Jeremy Heiler is correct, his justification is misconstrued. Why you're getting object instead of number has nothing to do with constructors.

The problem here is the this keyword. You need to understand what happens whenever you use this. A little bit of digging through the ECMA draft will show you that

The this keyword evaluates to the value of the ThisBinding of the current execution context.

(I would change the above. The this keyword doesn't evaluate to the value of anything as we'll soon see.) Hmm, okay, but how exactly does ThisBinding work? Read on!

The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList:

  1. If the function code is strict code, set the ThisBinding to thisArg.
  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.
  3. Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
  4. Else set the ThisBinding to thisArg.
  5. Let localEnv be the result of calling NewDeclarativeEnvironment passing the value of the [[Scope]] internal property of F as the argument.
  6. Set the LexicalEnvironment to localEnv.
  7. Set the VariableEnvironment to localEnv.
  8. Let code be the value of F’s [[Code]] internal property.
  9. Perform Declaration Binding Instantiation using the function code code and argumentList as described in 10.5

And therein lies the rub (look at the bolded part). If a function is ever called from a non-object context, ThisBinding (aka using this) always returns the value of the context wrapped inside an object. The easiest way to fix it would be to do:

Number.prototype.abs = function () {
    "use strict"; // if available
    // * 1 implicitly coerces 'this' to a number value
    return this >= 0 ? this * 1 : this * -1;
    //... or Number(this) explicitly coerces 'this' to its toNumber value
    return Number(this >= 0 ? this : this * -1);
};

...to coerce the this object (or to force strict mode). But I think it's important to understand how this works, and this question is a great example of that.

like image 147
David Titarenco Avatar answered Oct 22 '22 09:10

David Titarenco


This is because 50 by itself is a Number value, whereas you are returning a Number object.

alert(typeof 50); // number
alert(typeof (50).abs()); // object

http://jsfiddle.net/Pkkaq/

Section 4.3.21 of the ECMAScript reference:

A Number object is created by using the Number constructor in a new expression, supplying a Number value as an argument. The resulting object has an internal property whose value is the Number value. A Number object can be coerced to a Number value by calling the Number constructor as a function.

In other words, a Number value cannot be strictly equal to a Number object.

typeof 50 === typeof new Number(50) //--> false; number != object

The reason why (-50).abs() works as expected is because it is being multiplied by -1. When a Number object is multiplied by a Number value it becomes a Number value. In this case, if the parameter is positive, the object is simply returned untouched, causing an object to be returned.

Here is a fix for your method, according to the quoted reference above:

Number.prototype.abs = function () {
    return Number(this >= 0 ? this : this * -1);
};
like image 20
Jeremy Avatar answered Oct 22 '22 09:10

Jeremy


Triple equals (===) checks type as well as value to ensure they are equal. It fails for the same reason that new Number(5) !== 5 and new String("Text") !== "Text", in that they're different types. However, when you're using your negative absolute value, you're performing a mathematical computation which is outputting a raw number, and not a number object. Due to this, the type check matches and it's true.

like image 5
Robert Avatar answered Oct 22 '22 09:10

Robert


Math.abs(-50) === 50 works everywhere. So you could rewrite Number.prototype.abs to:

Number.prototype.abs = function () {
    return Math.abs(this);
};

I suppose. Tested Math.abs(50) === 50 in IE9 and Chrome 11, both: true

Other ways to make (50).abs() === 50 using your method may be:

return this >= 0 ? this.valueOf() : this * -1;
return this >= 0 ? this * 1 : this * -1;

So you extension doesnt return an object (this) when > 0, but a number value.

But I would advise to just use the already available Math.abs method, in which case you can be sure that Math.abs(50) === 50; returns true and you avoid an unnecessary monkey patch.

For completeness: from the selected answer it follows that using strict would be a solution too.

like image 5
KooiInc Avatar answered Oct 22 '22 08:10

KooiInc