Type conversion
A value's type decides what an operator does with it. Often you have the right value in the wrong type: the age someone typed is the text "21", not the number 21; a price needs to be shown as text with a currency sign. Type conversion turns a value of one type into another. You can do it on purpose (explicit conversion), and JavaScript also does it by itself in some situations (implicit conversion, called coercion). Knowing both stops a whole family of bugs.
Converting to a string
const n = 42;
console.log(String(n), typeof String(n));
console.log(n.toString(), (3.5).toString());
console.log(`${n}`); // a template literal converts too
console.log(String(null), String(undefined), String(true), String([1, 2]));String(x) works on everything. x.toString() works on most values but fails on null and undefined (they have no methods), so prefer String(x) when the value might be missing. Arrays convert to their items joined by commas.
Converting to a number
Number(x) converts the whole value, or gives NaN if it can't:
| Value | Number(value) | Why |
|---|---|---|
"42" | 42 | a clean number |
" 42 " | 42 | spaces at the ends are ignored |
"4.5" | 4.5 | decimals work |
"" | 0 | an empty string is 0 (a common trap) |
"42px" | NaN | not entirely a number |
true / false | 1 / 0 | |
null | 0 | |
undefined | NaN |
console.log(Number("42"), Number(" 42 "), Number("4.5"));
console.log(Number(""), Number("42px"), Number("abc"));
console.log(Number(true), Number(false), Number(null), Number(undefined));
console.log(+"7" + 1); // unary + does the same as Number()parseInt and parseFloat
parseInt and parseFloat read as much as they can from the start of a string and stop at the first character that doesn't fit. That makes them right for values with units, and wrong when you want to reject bad input:
console.log(parseInt("42px"), parseFloat("3.5kg"));
console.log(parseInt("12.9")); // 12: parseInt stops at the dot
console.log(parseFloat("12.9")); // 12.9
console.log(parseInt("abc42")); // NaN: no digits at the start
console.log(parseInt("101", 2)); // 5: read "101" as binary
console.log(parseInt("ff", 16)); // 255: read as hexadecimalThe second argument of parseInt is the radix, the number base. It's good practice to always give it, usually 10: parseInt(text, 10).
| Use | When |
|---|---|
Number(x) | the whole string must be a number (form input, data) |
parseInt(x, 10) | you want the whole-number part of "42px" or "12.9" |
parseFloat(x) | you want a decimal from "3.5kg" |
Converting to a boolean
Boolean(x) (or !!x) gives false for the eight falsy values (false, 0, -0, 0n, "", null, undefined, NaN) and true for everything else:
console.log(Boolean(""), Boolean("hi"), Boolean(0), Boolean(-3));
console.log(Boolean(null), Boolean([]), Boolean({}), Boolean("false"));
console.log(!!"text", !!0);The surprising ones: "false" and "0" are non-empty strings, so they are true, and empty arrays and objects are true too.
When JavaScript converts by itself
+ joins as soon as a string is involved
If either side of + is a string, the other side becomes a string and they are joined. Every other arithmetic operator converts to numbers:
console.log(1 + "2"); // "12"
console.log("3" - 1); // 2
console.log("3" * "4"); // 12
console.log("10" / "4"); // 2.5
console.log(true + 1); // 2: true becomes 1
console.log("5" + 5 - 5); // "55" - 5 = 50Expressions run left to right, so "5" + 5 - 5 first joins ("55"), then subtracts (50).
Comparisons
<, >, <= and >= compare two strings alphabetically, but if one side is a number, they compare numerically:
console.log("10" < "9"); // true: as text, "1" comes before "9"
console.log("10" < 9); // false: one side is a number, so 10 < 9
console.log(Number("10") < Number("9")); // false: convert both to be sure== converts, === doesn't
The loose equality == converts types before comparing, with rules that are hard to remember. The strict === never converts:
console.log("5" == 5, "5" === 5);
console.log(0 == "", 0 === "");
console.log(0 == false, null == undefined, null == 0);
console.log(NaN == NaN, NaN === NaN);Use === and !== everywhere. The only common use of == is x == null, which is true for both null and undefined, and even that is clearer written as x === null || x === undefined or with ??.
Checking for NaN
NaN isn't equal to anything, itself included, so x === NaN is always false. Use Number.isNaN:
const result = Number("hello");
console.log(result === NaN); // false, always
console.log(Number.isNaN(result)); // true
console.log(isNaN("hello")); // the old global isNaN converts first: true
console.log(Number.isNaN("hello")); // the strict version doesn't: falseValidating input
Everything a user types arrives as a string. Convert it, then check it before using it:
function readAge(text) {
const age = Number(text.trim());
if (text.trim() === "" || !Number.isInteger(age)) return "Please type a whole number";
if (age < 0 || age > 120) return "That age doesn't look right";
return `Next year you'll be ${age + 1}`;
}
for (const input of ["21", " 21 ", "", "21.5", "twenty", "-4"]) {
console.log(JSON.stringify(input), "->", readAge(input));
}Number.isInteger rejects decimals and NaN; Number.isFinite accepts any real number but rejects NaN and Infinity. The empty-string check matters because Number("") is 0.
The famous surprises, explained
| Expression | Result | Why |
|---|---|---|
[] + [] | "" | both arrays become empty strings, then join |
[] + {} | "[object Object]" | {} becomes the string "[object Object]" |
true + true | 2 | each true becomes 1 |
1 < 2 < 3 | true | 1 < 2 is true, and true < 3 is 1 < 3 |
3 > 2 > 1 | false | 3 > 2 is true, and true > 1 is 1 > 1 |
"b" + "a" + +"a" + "a" | "baNaNa" | +"a" is NaN, which joins as text |
console.log([] + [], [] + {}, true + true);
console.log(1 < 2 < 3, 3 > 2 > 1);
console.log(("b" + "a" + +"a" + "a").toLowerCase());None of these should appear in real code: they are what happens when types are mixed without thinking. Convert explicitly and compare with ===, and they never will.
Key points
String(x)converts anything to text;Number(x)converts the whole value or givesNaN.parseInt(x, 10)andparseFloat(x)read a number from the start of a string and ignore the rest.- Eight values are falsy;
"0","false",[]and{}are truthy. +joins when either side is a string;-,*,/convert to numbers.- Use
===; test forNaNwithNumber.isNaN; rememberNumber("")is0.
Practice
- Convert
"3.75"to a number three ways (Number,parseFloat, unary+) and add 1 to each. - Predict, then check:
"6" / "2","6" + 2,"6" - "2",6 + +"2". - Write
toNumberOrZero(text)that returns the number, or0when the text isn't a valid number. - Sort
["10", "9", "100", "2"]as numbers, not as text.