Observe this behaviour
"0" == false; // true
"0" == ""; // false
false == ""; // true
"" == []; // true
false == []; // true
"" == {}; // false
"true" == true; // false
"foo" == [ "foo" ]; // true
Javascript has been widely criticised by developers of all levels for having some particular unpredictable behaviour. Part of the reason for this unpredictability stems from how the javascript engine handles data types at runtime (dynamic typing).
This created so much burden for developers that a new "language" or "superset of javascript" as the creators like to call it, called Typescript was created to mitigate this problem. But dynamic typing in javascript isn't all so bad and can even be helpful as it can reduce verbosity of code. In fact, it is also very easy to understand once you learn the rules governing it.
In this article we will do just that. But first let's learn about data types
Data Types
These are the various forms in which data can exist in a program. In Javascript, there are seven built in types.
number
string
boolean
null
undefined
object (including arrays and functions)
symbol (not covered in this article)
All of these are primitives except objects.
Dynamic typing means the interpreter can assign whatever data type it deems fit to a variable and even convert a variable's data type at runtime. This is unlike static typing (in TypeScript, Java, C++) where we tell the compiler what data type we want our variables to be, and this persist throughout the program i.e the data type never changes except we explicitly tell it to.
The conversion of one data type to another is called coercion.
Dynamic typing allows for coercion to occur at runtime.
Coercion
As previously stated, this is the conversion of data from one type to another e.g from a string to a number, etc. This can be done in one of two ways. Explicit and Implicit
Explicit Coercion:
As the name implies, this involves explicitly telling our code to convert from one data type to another using a native object. Examples of native objects are ; String(), Number(), Boolean(), etc
var a = 42;
var b = String( a );
var c = "3.14";
var d = Number( c );
b; // "42"
d; // 3.14
Note: we do not add new keyword, we do not call it as a constructor as that returns an object
Implicit Coercion : Here coercion is done without us asking for it. This usually occurs when we use a data type in an operation that requires another data type, e.g using strings as operands in mathematical operations.
var a = "3.14";
var b = a - 1;
b; // 2.14 a gets converted to a number
This is the part developers deeply resent and criticize, the ability of javascript to change our data type and hence our programs without our permission, however implicit coercion is not always evil and can help in code readability as we will see going forward.
We will now look at specific examples of explicit and implicit coercion
Explicit Coercion
We will now look at specific results from coercing data types starting with numbers.
Numbers
Number to String
Coercion of numbers to strings involves passing the number as an argument to the String() native object
var a = 42; var b = String( a ); b // "42" String( 0 ); // "0" String (NaN) // "NaN" String(Infinity) // "Infinity"
Numbers to Boolean
Coercion to booleans involves using the Boolean() native object.
All numbers except 0 and Nan become true
var a = 42 var b = 0 var c = NaN var d = Infinity Boolean(a) // true Boolean(b) // false Boolean(c) // false Boolean(d) // true
Strings
Strings to numbers
Coercion of strings to numbers is done with the Number () native object. If the string is made up of numeric data, it becomes a number, otherwise is becomes NaN. An empty string is coerced to 0 (weird).
var a = "50" var b = "frontend okeke" var c = "" Number(a) // 50 Number(b) // NaN Number(c) // 0
Strings to Boolean
Coercion of strings to Boolean is done with the Boolean() native object. All strings become true while empty strings become false.
var a = "frontend okeke" var c = "" Boolean(a) // true Boolean(b) // false
Boolean
Booleans are easy and straightforward Boolean to String
var a = true
var b = false
String(a) // "true"
String(b) // "false"
Boolean to Number
var a = true
var b = false
Number(a) // 1
Number(b) // 0
Null and Undefined
The primitives null and undefined behave similarly when coerced
To string
var a = null var b; String(a) // "null" String(b) // "undefined"
To number
var a = null var b; Number(a) // 0 Number(b) // NaN (weird)
To boolean
var a = null var b; Boolean(a) // false Boolean(b) // false
Objects
Lastly, let's learn how objects are coerced to primitives. Objects contain two functions that aid in their coercion.
valueOf() // (has to be defined explicitly)
toString() //(exists in all objects)
These functions are called depending on if the object is to be coerced to a number of a string. You can already guess which is which.
However, if for a reason, one of these methods is absent, the other is consulted and whatever it returns is coerced further. i.e when coercing to a number, valueOf()
is called, if it is absent, toString()
is called (which returns a string) and that string is coerced further to a number. And vice versa for strings.
var a = {
valueOf: function(){
return 42;
}
};
var b = {
toString: function(){
return "42";
}
};
var c = {}
Number( a ); // 42
Number( b ); // 42
Number( c ); // NaN
String(a); // "[object object]" (toString() exists on the prototype
String( b ); // "42"
String( c ); // "[object object]"
Objects to String
The built in toString()
method that exists on the prototype returns the string "[object Object]"
for objects and "[ object Function]"
for functions
However Arrays have a modified toString()
method. When called by an array, toString()
returns the array as a concatenated string, with items in the array separated by commas. Empty arrays return empty strings.
var a = [1,2,3]
String(a) // "1,2,3"
String ([]) // ""
Number( [] ); // 0 (the empty string returned gets further coerced to 0)
Number( [ "abc" ] ); // NaN
Objects to Boolean
All objects are coerced to true, including empty objects and empty arrays.
var a = [];
var b = {};
var c = function(){};
Boolean( a ); // true
Boolean( b ); // true
Boolean( c ); // true
Native Objects and Constructors
Before learning about implicit coercion, there is one thing to take note of. When coercing values with native objects, the new
keyword is not used to call the object, i.e they are not called as constructor functions. This is because when called as constructors an object is always returned. This object wraps the coerced value and can bring about some weird behaviour
var a = new Boolean( false );
var b = new Number( 0 );
var c = new String( "" );
Boolean( a ) // true;
Boolean( b ) // true;
Boolean( c ) // true;
// a, b and c are objects now and not primitives anymore
Note: calling native objects as constructors is the same as calling passive primitive values as arguments to the Object class. In a way this can be considered coercion to the object type.
var a = new Number( 42 );
//is same as
var a = Object(42)
Implicit Coercion
As previously stated, this is when data is coerced to a different type without it being asked so.
Implicit Boolean
This occurs when operations that expect their operands to be Boolean but are something else. The operands are therefore coerced to booleans.
Examples are:
The test expression in an
if()
statement.The test expression (second clause) in a
for()
header.The test expression in a
while/do while
loop.The test expression in a ternary operation.
The left operand of the
||
and&&
logical operators.
This is perhaps the most common case of implicit coercion and even the biggest critics of javascript have enjoyed to benefit this sort of coercion offers.
Mathematical operations
Mathematical operations such as addition and subtraction usually involves two numeric operands. However when a non-numeric operands is passed in, you can guess what happens, it gets coerced to a number.
var a = "3.14";
var b = a - 1;
b; // 2.14 a gets converted to a number
Note: There is a little quirk to this behaviour. The mathematical operator for addition has a second function which is string concatenation. This second function in fact takes a higher precedence over numeric addition. The +
operator only adds two operands when they are numbers, otherwise it concatenates them. That is to say if both operands are not numbers, they get coerced to strings and concatenated instead.
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
a + d; // "420"
b + c; // "420"
Equality Operator.
This is perhaps the most unpredictable of all javascript behaviours and also where receives the most criticism so get ready as we explore this forbidden land. The equality operator compares two operands and returns true if they are considered equal and false otherwise.
There are two types
Loose equality
==
Strict equality
===
It is a common misconception that loose equality operator only checks the values while strict equality checks both value and type. However, according to the javascript specification, the difference is: the strict equality operator does not allow coercion of any of the operands while the loose equality allows. To paraphrase, when comparing two values using the loose equality operator, the javascript engine coerces either or both of the operands till they are both primitives and of the same type. Phew.
Now we know this, let us now proceed further to learn the rules determining how values are coerced.
When comparing a string and a number, the string is always coerced to a number
var a = 42; var b = "42"; a === b; // false a == b; // true ("42" gets coerced 42)
When comparing a Boolean and any other value, the boolean is always coerced to a number
var x = true; var y = "42"; x == y; // false (true gets coerced to 1, and 1 is not equal to 42)
When comparing an object and a string or number the object is always coerced to a string or number depending on the other operand
var a = 42; var b = [ 42 ]; a == b; // true (42 gets coerced to "42" which is further coerced to 42)
Null and undefined are equal to each other and nothing else
var a = null; var b; /*variables declared but not assigned a value have undefined as their value */ a == b; // true a == null; // true b == null; // true a == false; // false b == false; // false a == ""; // false b == ""; // false a == 0; // false b == 0; // false
Every other comparison is false
With these rules stated, we can revisit our initial paragraph and understand how that works now.
"0" == false; // true
// (false gets coerced to 0 and "0" gets coerced to 0)
"0" == ""; // false
//(no coercion occurs, they are both strings)
false == ""; // true
//(false gets coerced to 0, an empty string gets coerced to 0)
"" == []; // true
//(an empty array gets coerced to an empty string)
false == []; // true
/*(false gets coerced to 0,
an empty array gets coerced to an empty string which further gets coerced to 0)*/
"" == {}; // false
//(an empty object gets coerced to "[object Object]"
"true" == true; // false
//true gets coerced to 1, "true" gets coerced to NaN)
"foo" == [ "foo" ]; // true
//(["foo"] gets coerced to "foo"
Conclusion
Now that we have explored most of what you need to know about coercion it is now left for you the reader to decide you way forward. Either to accept javascript and it's minor quirks, constantly finding a way around it or to completely abandon the language and find solace elsewhere. If you decide like me to continue with our beloved javascript, the key is to understand high risk situations where unpredictability can arise in code and avoid them or explicitly coerce your data types, most especially to help readers of your code understand your code. Goodluck, keep hacking.