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?
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:
- If the function code is strict code, set the ThisBinding to thisArg.
- Else if thisArg is null or undefined, set the ThisBinding to the global object.
- Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
- Else set the ThisBinding to thisArg.
- Let localEnv be the result of calling NewDeclarativeEnvironment passing the value of the [[Scope]] internal property of F as the argument.
- Set the LexicalEnvironment to localEnv.
- Set the VariableEnvironment to localEnv.
- Let code be the value of F’s [[Code]] internal property.
- 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.
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);
};
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With