Published on

Reading notes from Effective JavaScript - Implicit Coercions

Authors

Intro

In the series of reading notes, I will share highlights from some of the technical books I've been reading.

Let's start with "Effective JavaScript" by David Herman. This post is about implicit coercion, a feature of JavaScript which allows one type to be transformed into another on the go, without a developer to specify it. While looking very convenient, the feature requires caution and good understanding in its usage. Let's find out more.

Cases

Understanding implicit coercion can help avoid bugs and write more predictable code. JavaScript is loosely types language which tries to be flexible in operations with multiple types. This could lead to errors and bugs, so let's explore some common cases of confusion.

Let's go through some of the most common cases:

  1. String and Number Coercion
    • Example: '5' + 3 and '5' - 3
    • Explanation: The + operator can act as both an addition and concatenation operator, leading to potentially unexpected results. In contrast, the - operator only works with numbers, causing JavaScript to convert string operands into numbers.
  2. Boolean Coercion in Logical Operations
    • Example: Boolean('0') and !!{}
    • Explanation: Almost all strings and objects are truthy, except for a few falsy values like 0, null, undefined, '', NaN, and false itself.
  3. Coercion in Conditional Statements
    • Example: if ('false') { ... }
    • Explanation: Despite the string 'false' being semantically false, it is truthy in JavaScript, so the code block will execute.
  4. Array Coercion
    • Example: [] == false and [0] == false
    • Explanation: Arrays are objects, and when compared to boolean values, they are converted to strings or numbers, leading to potentially unintuitive outcomes.
  5. Object Coercion
    • Example: {} + [] and [] + {}
    • Explanation: The plus operator triggers different behavior when dealing with objects and arrays, leading to concatenation or conversion to numbers, resulting in surprising results.

Some code snippets

Let's keep exploring implicit coercions with more specific examples.

  1. String and Number Coercion
console.log('5' + 3); // '53'
console.log('5' - 3); // 2

console.log('5' * '2'); // 10 (multiplication coerces strings to numbers)
console.log('5' * '2a'); // NaN (non-numeric string cannot be coerced to a valid number)

console.log('5' + null); // '5null' (null is coerced to a string)
console.log('5' + undefined); // '5undefined' (undefined is coerced to a string)
console.log('5' + true); // '5true' (boolean true is coerced to a string)
console.log('5' - true); // 4 (boolean true is coerced to number 1 before subtraction)
  1. Boolean Coercion in Logical Operations
console.log(Boolean('0')); // true
console.log(!!{}); // true
console.log(!!'false'); // true (non-empty strings are truthy)
console.log(!!''); // false (empty string is falsy)
console.log(!!-1); // true (non-zero numbers are truthy)
console.log(!!NaN); // false (NaN is falsy)
console.log(!!null); // false (null is falsy)
console.log(!!undefined); // false (undefined is falsy)
  1. Coercion in Conditional Statements
if ('false') {
	console.log('This will execute.');
}

if (0) {
    console.log('This will not execute.');
} else {
    console.log('0 is falsy.');
}

if (NaN) {
    console.log('This will not execute.');
} else {
    console.log('NaN is falsy.');
}

if ('') {
    console.log('This will not execute.');
} else {
    console.log('Empty string is falsy.');
}

if ([]) {
    console.log('This will execute.'); // Arrays are truthy, even empty ones
}
  1. Array Coercion
console.log([] == false); // true
console.log([0] == false); // true
console.log([1, 2] + [3, 4]); // '1,23,4' (arrays are converted to strings and then concatenated)
console.log([1] == 1); // true (array coerced to string, then to number)
console.log([1, 2] == '1,2'); // true (array coerced to string)
console.log([null] == ''); // true (array with null coerced to empty string)
console.log([undefined] == ''); // true (array with undefined coerced to empty string)
  1. Object Coercion
console.log({} + []); // "[object Object]"
console.log([] + {}); // "[object Object]"
console.log({} + []); // "[object Object]" (both operands are converted to strings)
console.log([] + {}); // "[object Object]" (order doesn't affect the coercion result)
console.log({} + [1]); // "[object Object]1" (array is coerced to a string)
console.log([1] + {}); // "1[object Object]" (array is coerced to a string)
console.log({a: 1} + {b: 2}); // "[object Object][object Object]" (objects are converted to strings)
console.log([1, 2] + {0: 'a', 1: 'b'}); // "1,2[object Object]" (object is coerced to a string)

How to navigate implicit coercions safely

Strict comparison operators

Use strict comparison operators (=== and !==) to avoid coercion in comparisons. Unlike the abstract comparison operators (== and !=), which perform type conversion if the types of the operands differ, the strict comparison operators do not perform any type coercion. This means that for the comparison to return true, both the value and the type of the operands must be the same.

Type casting

Another approach is to explicitly convert types when necessary using functions like Number(), String(), or Boolean().
Explicit type conversion, also known as type casting, is a technique where developers intentionally convert values from one data type to another. This approach is fundamental in JavaScript due to its dynamic nature and can help prevent unexpected behavior from implicit coercion.

Examples

Here are some useful type castings in action:

let flightNumberString = "747";
let flightNumber = Number(flightNumberString);
console.log(flightNumber + " (Type: " + typeof flightNumber + ")"); // 747 (Type: number)

let seatCount = 300;
let seatCountString = String(seatCount);
console.log(seatCountString + " (Type: " + typeof seatCountString + ")"); // "300" (Type: string)

let isFirstClass = true;
let firstClassStatus = String(isFirstClass);
console.log(firstClassStatus + " (Type: " + typeof firstClassStatus + ")"); // "true" (Type: string)

let destinationString = "Paris";
let destinationAvailability = Boolean(destinationString);
console.log(destinationAvailability + " (Type: " + typeof destinationAvailability + ")"); // true (Type: boolean)

let baggageCount = 0;
let baggageCheck = Boolean(baggageCount);
console.log(baggageCheck + " (Type: " + typeof baggageCheck + ")"); // false (Type: boolean)

let temperatureCelsius = 27;
let weatherForecast = Boolean(temperatureCelsius);
console.log(weatherForecast + " (Type: " + typeof weatherForecast + ")"); // true (Type: boolean)

let nullIsland = null;
let islandElevation = Number(nullIsland);
console.log(islandElevation + " (Type: " + typeof islandElevation + ")"); // 0 (Type: number)

let undefinedRoute;
let routeDescription = String(undefinedRoute);
console.log(routeDescription + " (Type: " + typeof routeDescription + ")"); // "undefined" (Type: string)

let hotelRatingString = "4.5stars";
let hotelRatingInt = parseInt(hotelRatingString);
let hotelRatingFloat = parseFloat(hotelRatingString);
console.log(hotelRatingInt + " (Type: " + typeof hotelRatingInt + ")"); // 4 (Type: number)
console.log(hotelRatingFloat + " (Type: " + typeof hotelRatingFloat + ")"); // 4.5 (Type: number)

let emptyFlight = "";
let flightFull = Boolean(emptyFlight);
console.log(flightFull + " (Type: " + typeof flightFull + ")"); // false (Type: boolean)

let openSeats = [];
let seatsAvailable = Boolean(openSeats);
console.log(seatsAvailable + " (Type: " + typeof seatsAvailable + ")"); // true (Type: boolean)

Best Practices for type casting

  • Know When to Convert: Use explicit type conversion when receiving input from external sources (like user input, API responses) or when preparing data for output (like rendering in the UI or sending to an API).
  • Check Before You Convert: Especially when converting to numbers, check if the conversion is possible or if it would result in NaN, which can lead to further complications in your calculations.

Conclusion

Implicit coercions could be tricky, and understanding this feature of JavaScript will help save time on bugfixing. In the examples above, we made an overview of some of the most common cases. Then, we learned the best practices on how to avoid side effects coming with implicit coercions.