How come I can "divide" an object by a number?
When you execute an arithmetic operation on an object, its valueOf
function is implicitly called.
Try the following to have an object that you can actually divide:
function Foo() {
this.valueOf = function() {
return 500;
};
}
var bar = new Foo();
console.log(bar/1000); // -> 0.5
Late update
Given that my answer contained some incomplete and -at times- outright wrong information, I thought it best to correct my mistakes. Albeit late, here goes:
Almost All JS objects have 23 methods in common: valueOf()
in case of the date object, it maps back to the getTime()
method, which returns a number. What this number is, is the number of milliseconds since jan 1st 1970, Many programming languages use this date to base their dates on. It's referred to as the Unix Timestamp, the date is called the Unix Epoch. in case you were wondering.
Another method is toString
, which returns a string (obviously).
The third method is hasOwnProperty
, which can be traced back in the prototype-chain to Object.prototype
, but that's not important for this question.
Whenever you compare 2 values (objects or not), JS will coerce the types of both operands in such a way that the two values can be safely compared (1 == '1'
coerces the string '1'
to a Number
, for example).
This type-coercion also applies when an object or primitive value is concatenated into a stirng, or when an expression is evaluated to a singular value (eg in your console, type new Date;
, and you'll see what I mean).
The default behaviour of the Date
object is to be coerced to a string, the toString
method is invoked.
So, the Date
instances are no different. They are objects, which means the variables' values will be references to objects. However in your expression/statement, you're using an arithmetic operator (/
) on the object. A string wouldn't make much sense in this context, so JS falls back to the valueOf
method (sort of, Date
's can be coerced to numbers in a variety of ways). This does yield a Number
, which can be divided effortlessly.
The same behaviour can be expected (and can indeed be observed) when using the comparison operators >
, <
, <=
and >=
.
This means that comparing dates, and using them to generate output is, most of the time, a doddle:
var d1 = new Date();
var d2 = new Date(1234567890123);
if (d1 > d2)
{
//do stuff
}
When written in full, this is:
Date.prototype.valueOf = Date.prototype.getTime; //<-- this isn't how its done, but think of the valueOf method as doing something similar
if (d1.getTime() > d2.getTime()) //OR:
if (d1.valueOf() > d2.valueOf())
There are other advantages, too, apart from comparison:
var d3 = new Date(d1 - 1000); //new date, 1 second before d1
But there is a trade-off/pitfall:
As with all objects in JS, equality checks using ==
or ===
is a bit odd at first:
var a = new Date();
var b = new Date(a.valueOf());
console.log(a.toString() === b.toString());//true
console.log(a.valueOf() === b.valueOf());//true, to the milisecond
console.log(a == b);//false!
console.log(a === b);//false!!
Like I said: variables that are assigned objects don't actually hold the value of that object, they reference it. Since both a
and b
reference a different instance of the same object, they are not equal. Also because they're both objects (of the same type, to that) there is no type coercion going on.
Think of it as 2 houses, 100% equal in every way, except for the address. If there is no coercion, you're actually saying something like:
"There's a house on Fiction street 1, and on Fiction Street 2. Are these two houses equal?" To which the answer is a resounding no.
To fix this, you'll have to explicitly ask JS to compare the way these houses look by coercing them to another type manually. This is a lot easier than it sounds:
console.log(a.valueOf() == b.valueOf());//true
//or shorter, still:
console.log(+(a) == +(b));
//coerce to strings, either call toString, or:
console.log((''+a) == (''+b));
Again this may seem silly at first, but this way, you can at least test if two variables actually do reference the same instance. Suppose we have 2 objects taking up memory, when we really only need 1, we can free the reference to one of these instances to be GC'ed:
if (a !== b && +(a) === +(b))
{//their values are the same, but different instances
b = a;//now b references the same object as a
}
console.log(a === b);//true
console.log(+(a) === +(b));// true again
Now there still are a few oddities, mind you, that affect the Date
object in particular. Try to guess what the following statements would log:
a.setTime(a.getTime() + 1000);//add 1 second
console.log(a == (b + 1000));
console.log(a === (b + 1000));
console.log((a-1000) == b);
console.log((a-1000) === +b);
The answer is: false, false, false and true. How come?
The first case is simple: the +
operator is overloaded, it also concatenates strings, and the Date
object's default behaviour is to toString
itself, so b+1000
is evaluated as b.toString() + '1000'
.
Next, using the type and value checking ===
will almost always be false, since no coercion is going on.
Then, by subtracting 1000 using the not overloaded -
arithmetic operator means that the left operand will still be evaluated to a number. The right operand, however will still revert to its befault behaviour of being coerced to a string. They are not equal.
The last example explicitly coerces the right operand to a number, and yields true.
To get true
in all of the above cases, here's what to write
console.log(+a == (+b + 1000));
console.log(+a === (+b + 1000));
console.log((a-1000) == +b);
console.log((a-1000) === +b);
Simply explicitly coerce your Date
instances and you should be OK
Because when you use the divide operator, new Date()
will be converted inplicitly to an number (representing the total milliseconds of this date starting at january 1th of 1970). The +
operator will not work, since +
in strings means concatenation, not sum, and when you try to sum a Date it will not return the milliseconds, but the toString() of that date, but *
will work since String don't have the *
operator. For example, try new Date() * 2
.
For more details about the Date global object, take a look here: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date
Edit: I said that the date will be converted to a String when it is in an expression with / and I was wrong of course. I already corrected the answer. There are some comments below that can clarify even more my answer. The behavior of the String to Number still works, i.e., a String that represents a valid number can be multiplied, divided and subtracted, but not added since the sum operator is used to concatenate Strings. Take a look:
console.log( "10" + "1" );
console.log( "10" - "1" );
console.log( "10" * "2" );
console.log( "10" / "3" );
console.log( 10 + "1" );
console.log( 10 - "1" );
console.log( 10 * "2" );
console.log( 10 / "3" );
console.log( "10" + 1 );
console.log( "10" - 1 );
console.log( "10" * 2 );
console.log( "10" / 3 );
Before division date object will be converted to number which will give the milliseconds from January 1st 1970
new Date()/1000 => Number(new Date()) / 1000 = some number
Most languages actually represent Dates in their own internal language as just a number. The convention that Javascript uses is number of milliseconds since January 1, 1970, and many other languages have a similar convention. This is the same reason that you can simply subtract dates from each other in the same way that a timing method works.
©2020 All rights reserved.