On the surface your question looks like the sheer difference between ==
and ===
operators, but in fact there is bit more to it.
For your first question, since javascript is not strictly typed language, there is 2 operators, ==
will try to convert the left operand to right ones type if possible whereas ===
will give false if the types are different with the exception of NaN
.
A more interesting question is when toString
method gets called. Normally when you create an object, either by typing an object literal or through a constructor, it will inherit toString
from the Object, you easily check this:
var u = function(){};
var w = {};
u.prototype.toString === Object.prototype.toString //true
w.toString === Object.prototype.toString //true as well
Now what you might forget is that there is also a valueOf
method:
u.prototype.valueOf === Object.prototype.valueOf //true
w.valueOf === Object.prototype.valueOf //true as well
But what does it do? I points to itself:
w.valueOf === w //true
u.prototype.valueOf() === u.prototype //true as well
So when an object is given, the first choice is to use the toString
becuae valueOf
will result in the object itself. What does toString
give by default? it can depend on what is there on its prototype chain:
w.toString() //"[object Object]"
u.toString() //"function (){}"
u.prototype.toString.call(u) //"[object Function]"
So by default the first choice is to use the nearest toString
method of the object. Now to see what happens if we override BOTH valueOf
and toString
, let me construct this object:
var x = {
inner:10,
ledger:[],
result:[],
timeout:0,
toString:function(){
console.log("String");
clearTimeout(this.timeout);
this.timeout = setTimeout((function(){
this.result = this.ledger;
this.ledger = []}).bind(this)
,0) ;
this.ledger.push("toString");
this.ledger.slice(-2);
return String(this.inner);
},
valueOf:function(){
console.log("Valueof");
clearTimeout(this.timeout);
this.timeout = setTimeout((function(){
this.result = this.ledger;
this.ledger = []}).bind(this)
,0) ;
this.ledger.push("valueOf");
this.ledger.slice(-2);
return this.inner;
}
}
This object will keep a "ledger", an array of valueOf
or toString
or both called during type conversion. It will also console.log
. So,here are some operations that will trigger type conversion just like ==
:
+x //10
//ValueOf
"5" + x //"510"
//ValueOf
x + [] //10
//ValueOf
x + "str"//"10str"
//ValueOf
x.toString() //"10"
//String
String(x) //"10"
//String
x + {u:""} //"10[object Object]"
//valueOf
So in many cases if a non-default valueOf is found that is used. If the right operand is a string, then the returned value from valueOf
is converted to string rather than toString
on the object. To override default behavior you can force call to string by using toString
or String(
as shown in the examples. So the priority list goes as:
Custom valueOf >> custom toString >> default toString >>>> default valueOf