[object Object] Things You Didn’t Know About valueOf
Okay, fine. That title is horrible. But we’re all competing with BuzzFeed for readers now, and I needed something catchy, so here we are.
Right, then.
So, what is Object#valueOf
and why should you care?
More or less, it's a method that JavaScript calls automatically any time it sees an object in a situation where a primitive value is expected.
Let's look at a quick example.
var obj = {};
console.log( 7 + obj ); // "7[object Object]"
Plenty has been written about the insanity of implicit type conversion in JavaScript, so I'm really not interested in going down that road. Hopefully, your takeaway from the example is that we used the addition operator with a number on one side and an object on the other, and we got back a value that doesn't really make a ton of sense.
You can hardly blame JavaScript here, though. It has no way to know how to convert our object into a number.
But as luck would have it, we can actually define that behavior.
var obj = {
valueOf: function() {
return 42;
}
};
console.log( 7 + obj ); // 49
See? I wasn't lying earlier. The EcmaScript specification dictates that whenever an object is encountered in a situation where a primitive is expected, that object's valueOf
method will be called*.
* Actually, sometimes toString() will be called instead – but I'm not gonna get into it here. You can assume that any time a Number is expected, valueOf will be called. If you're super interested in this stuff, look up the ToPrimitive abstract operation in the ES spec.
Who cares?
Look, I’m not gonna lie to you here (I feel like we’re becoming friends, and I have a pathological need to be trusted). Knowing about valueOf
is not going to materially change the way you write code on a day-to-day basis. This post is really about surfacing an interesting little JS feature that a lot of people don’t know about. But that being said, there are some practical applications.
Let's write a little Integer
class. I won't go into much detail here, since I will assume that you, esteemed reader, are already intimately familiar with the concept of integers.
You can actually just skip this part if you want...
function Int( val ) {
if ( !( this instanceof Int ) ) {
return new Int( val );
}
if ( isNaN( val ) ) {
throw new TypeError(‘Int value must be a number’);
}
if ( val % 1 !== 0 ) {
throw new TypeError(‘Int value must be an integer’);
}
if ( val instanceof Int ) {
return Int( val._value );
}
this._value = Math.floor( val );
}
Int.prototype.add = function( n ) {
return Int( this._value + Int( n )._value );
};
Int.prototype.subtract = function( n ) {
return Int( this._value — Int( n )._value );
};
Int.prototype.multiply = function( n ) {
return Int( this._value * Int( n )._value );
};
Int.prototype.divide = function( n ) {
try {
return Int( this._value / Int( n )._value );
} catch ( e ) {
throw new Error(‘Division resulted in non-integer quotient’);
}
};
Okay. Now we've got an Int
class.
It does some type checking. It makes sure you don't pass it a float. And it has some basic arithmetic methods.
Here are a couple quick examples, just to give you a better idea of what this thing looks like:
Int( 6 ).multiply( 2 ); // { _value: 12 }
Int( 4 ).divide( 2 ).add( 3 ); // { _value: 5 }
Simple enough. But what if I need to do something like this?
var a = Int( 6 );
var b = Int( 11 );
Math.min( a, b ); // NaN
It basically blows up. I mean, it doesn't throw an exception, but it gives me NaN
, which is almost as useless.
I trust at this point that you see where we're headed.
Int.prototype.valueOf = function() {
return this._value;
};
Easy as that. Now, we'll try that same example – but this time, our Int
class has a valueOf
method on its prototype.
var a = Int( 6 );
var b = Int( 11 );
Math.min( a, b ); // 6
Not bad, right? We can also use instances of Int
in other places where a primitive value is expected:
Math.pow( Int( 9 ), 0.5 ); // 3
The More You Know™
If you liked this post, follow me on Medium or Twitter for more. I'm challenging myself to write every day for the next month.
And if you live in the Boston area and want to work on crazy, top-secret things with me at Project Decibel, shoot me an email. I'm hiring.