//Equality 5 == '5'; //true -- presumably '5' is converted to a number '5' == 5; //also true -- presumably 5 is converted to a string true == '5'; //true -- 1 is converted to boolean true true == 10; //true -- 10 -> boolean true == 'foo'; //false -- string doesn't coerce false == 'foo'; //false -- as you can see, 'foo' isn't true or false //Concatenation (or not) console.log("The answer is " + 55); //55 is converted to a string and concatenated 1 + '2'; //12 -- 1 is converted to a string 5 - '1'; //4 -- '1' is converted to a number //Inequality 1 < '2'; //true -- '2' is converted to a number '3' > 2; //true -- '3' is converted to a number 1 < 'foo'; //false 1 > 'foo'; //false //Arithmetic 5 + 2; //7 -- although under the covers this is actually 7.0 10 + 8.123; //18.123 -- 10 is immediately converted to a floating point number 0x0F + 3; //18 -- Hexidecimal number is converted directly to number type //Other oddities 1 == true && -1 == true; //true, and null == false; //true 'abc'.indexOf('e'); //-1, NOT null, so 'abc'.indexOf('e') == true; //true, but we wanted 'abc'.indexOf('e') >= 0;
As you can see, there isn’t a particularly hard and fast rule that one type is always converted to another. More importantly, you can see that the most common cases are strings to and from numbers and vice versa. Numbers coerce to booleans, but strings don’t. For concatenation numbers coerce to strings. For equality it’s unclear which direction the coercion goes and for inequality, strings are coerced to numbers as long as they convert cleanly.
Since type coercion is unpredictable, we should manage values ourselves and try to be as explicit as possible so we always get results back that we expect. We don’t want addition to concatenate our members if one is accidentally a string. We don’t want to coerce boolean values to numbers or the other way around since the only number that evaluates to false is 0 and there are many times we get values which mean something failed, but the coercion would make them true.
We, basically, don’t want the language to guess what we mean because it is likely to guess wrong. Let’s have a look at some of the things we can do to help improve the reliability of our applications and manage the type coercion that happens with our values throughout our source code.
First, let’s take a look at triple-equals (===). Performing a value conversion at comparison time has two pitfalls. The lesser of the two is, it’s slow. It’s not slow in the way that an O(n^4) algorithm is slow, but it is slower than comparing values directly without conversion. Let’s take a look:
1 == '1'; //true -- We saw this above. true == 'true'; //false -- a string cannot convert directly to a boolean -1 == true; //false 1 == true; //true -- true and 1 cross-convert to be equivalent //Let's normalize. 1 === '1'; //false -- a number is never equal to a string true === 'true'; //false -- a boolean is never equal to a string -1 === true; //false -- a boolean is never equal to a number 1 === true; //false -- same as above
We can see how eliminating coercion from our comparison operations, we get a normalized, type-safe experience while programming. This provides guarantees we otherwise could never get. If the code is changed, potentially in an unstable way, issues will start to emerge that will give us more immediate insight into what is happening.
//Numbers 1*'4' + 1; //5 -- This feels like a hack +'4' + 1; //5 -- This looks like a mistake //Typecasting to numbers instead Number('4') + 1; //5 //Booleans !'foo'; //false -- strange feeling, but it works !!'foo'; //true -- Gross. It's hacky and I'm just as guilty as anyone of doing this !!''; //false -- What does not-not empty string even really mean? //Typecasting to booleans instead Boolean('foo'); //true !Boolean('foo'); //false Boolean(''); //false //Strings; Yes, I have seen this example in the wild '' + 1234; //'1234' -- This relies on that weird coercion we were talking about //Typecasting to strings instead String(1234); //'1234'
Typecasting might take a few more keystrokes than one of the hack methods, but it does two things for us that other methods don’t provide. First, typecasting is declarative of intent. By using Boolean or Number, you know precisely what you should be expecting. You will also, get a highly normalized, safe value back. The second thing typecasting offers is a 100% guaranteed type safe expression every time. This means that every comparison, computation, concatenation, etc, will produce a predictable result. Predictability is stability.
Before we finish up, let’s take a look at a couple of other built-in functions that are useful for handling common conversion cases. These functions are specifically for managing number and string outputs. These three functions are parseFloat, parseInt and toString.
parseFloat('123.45'); //123.45 parseFloat('0xFF'); //0 -- x and F are not valid numbers in decimal floating point parseFloat('0107'); //107 -- Octal string is resolved as a decimal parseInt('1234', 10); //1234 -- base 10 numbering; the most common output parseInt('0xFF', 16); //255 -- Hexadecimal string parseInt('0107', 8); //71 -- Octal string parseInt('101', 2); //5 -- Binary string ['a', 'b', 'c'].toString(); //'a, b, c' 1234.toString(); //'1234'
The important take-away here is using type coercion is, at best, an unstable way to write programs which could introduce bugs that are difficult to identify and, at worst, a hack that makes code obscure and difficult to maintain. Instead of using type coercion, prefer stable, predictable methods for guaranteeing type and maintain type safety in your programs.