parseInt reads text and pulls out a whole number. map is a helper that hands your function each item, but it also tells it the item's position in the line, counting from zero. parseInt hears that position and thinks it is being told which number system to read in. Number ignores the position, so it never gets confused.
The curse
console.log(["1", "2", "3"].map(parseInt)); // prints: [ 1, NaN, NaN ]
You would expect [1, 2, 3]. Instead you get one real number and two broken ones.
What map really hands your function
map calls your function with three arguments every time. It passes the item, its position (the index), and the whole array. Most functions ignore the extras. parseInt does not, because its second parameter is the radix, the number system to read in. 10 means normal numbers, 2 means binary, 16 means hexadecimal.
So map(parseInt) really runs these three calls.
console.log(parseInt("1", 0)); // prints: 1
console.log(parseInt("2", 1)); // prints: NaN
console.log(parseInt("3", 2)); // prints: NaN
- A radix of
0is treated as "not given", so"1"is read as a normal number. - A radix of
1is not allowed (the valid range is 2 to 36), so you get NaN. - Radix
2is binary, which only has the digits 0 and 1."3"has no valid digit, so you get NaN.
How to break the curse
console.log(["1", "2", "3"].map(Number)); // prints: [ 1, 2, 3 ]
console.log(["1", "2", "3"].map((s) => parseInt(s, 10))); // prints: [ 1, 2, 3 ]
Number only looks at its first argument, so the index can't confuse it. If you do need parseInt, wrap it in a small arrow function that passes only the value, plus 10 for normal numbers.
The two behave differently on messy text. Number("12px") is NaN and Number("") is 0, while parseInt("12px", 10) is 12 and parseInt("", 10) is NaN. Pick the one that matches what you want to accept.
The same trap waits for any function that has optional extra parameters. Handing a function straight to map is only safe when you know it ignores the extra arguments.
Bonus twist: parseInt(0.0000005)
console.log(parseInt(0.0000005)); // prints: 5
console.log(String(0.0000005)); // prints: 5e-7
parseInt works on text, so it turns the number into a string first. Very small numbers are written in exponent shorthand, "5e-7", which means 5 times ten to the power of minus 7. parseInt reads the 5, stops at the e because it is not a digit, and returns 5.
To cut the decimals off a number, use Math.trunc or Math.floor. Keep parseInt for reading text.
Rule of thumb
Don't hand parseInt straight to map, use .map(Number) instead.
Calling parseInt(text) without a radix. It mostly works, but text that starts with 0x is read as hexadecimal. Write parseInt(text, 10) so the result never depends on how the text starts.
Check yourself
map calls parseInt("2", 1, array). The second argument is the radix, and a radix of 1 is outside the valid range of 2 to 36.