Why does ['1', '7', '11']. Map (parseInt) return [1, NaN, 3] in Javascript?
- Transfer
Javascript is weird. Do not believe? Well then try to convert the array of strings to integers using map and parseInt . Launch the console (F12 on Chrome), paste the code below and press Enter
['1', '7', '11'].map(parseInt);
Instead of the expected array of integers [1, 7, 11] we get [1, NaN, 3] . But how so? To find out what this is all about, we’ll first have to talk about some basic Javascript concepts. If you need TL; DR, scroll through the article to the very end.
Truthfulness and falsity
Here is a simple if-else statement in Javascript:
if (true) {
// всегда выполняется
} else {
// не выполняется никогда
}
In this case, the statement condition is always true, so the if block is always executed, and the else block is always ignored. This is a trivial example, because true is a Boolean type. What if we set a non-Boolean condition?
if ("hello world") {
// выполнится это?
console.log("Условие истинно");
} else {
// или это?
console.log("Условие ложно");
}
Try running this code in the developer console. You should see “The condition is true,” since the string “hello world” is perceived as true .
Each object in Javascript is perceived as either true or false . When placed in a logical context, such as an if-else statement, objects are treated as true or false based on their "truth." Which objects are true and which are false? A simple rule applies:
All values are true except false , 0 , "" (empty string), null , undefined , and NaN .
Counter intuitively, this means that the string “false” , the string “0” , the empty object {} and the empty array [] are true. You can verify this yourself by passing the Boolean function to any of the objects above (for example, Boolean ("0"); ).
But for our purposes, just remembering that 0 is a lie.
Base number system
0 1 2 3 4 5 6 7 8 9 10
When we count from zero to nine, we use different characters for each of the numbers (0-9). However, as soon as we reach ten, we need two different characters (1 and 0) to represent the number. This is due to the fact that we use the decimal number system.
Base is the smallest number that cannot be represented by just one character. Different number systems have different bases, and therefore, the same numbers can mean different numbers.
DECIMAL BINARY HEXADECIMAL
RADIX=10 RADIX=2 RADIX=16
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
16 10000 10
17 10001 11
For example, the numbers 11 indicate different numbers in these three number systems. For binary, it's the number 3. For hex, it's the number 17.
An attentive reader probably noticed that the code with parseInt returns 3 when the input is 11, which corresponds to the binary column from the table above.
Function Arguments
Functions in Javascript can be called with any number of arguments, even if their number in the signature is excellent. Missing parameters are considered undefined, and additional parameters are simply ignored (but stored in an array-like arguments object ).
function foo(x, y) {
console.log(x);
console.log(y);
}
foo(1, 2); // выводит 1, 2
foo(1); // выводит 1, undefined
foo(1, 2, 3); // выводит 1, 2
map ()
We are almost there!
Map is a method in a prototype array that returns a new array from the results of a function call for each element of the original array. For example, the following code multiplies each element of an array by 3:
function multiplyBy3(x) {
return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result); // выводит [3, 6, 9, 12, 15];
Now suppose I want to output each element using map () (and not using return ). You can simply pass console.log as an argument to map () ... right?
[1, 2, 3, 4, 5].map(console.log);
Something strange is happening. Instead of only outputting a value, each call to console.log prints the index and array completely.
[1, 2, 3, 4, 5].map(console.log);
// эквивалентно:
[1, 2, 3, 4, 5].map(
(val, index, array) => console.log(val, index, array)
);
// и НЕ эквивалентно:
[1, 2, 3, 4, 5].map(
val => console.log(val)
);
When passing the function to map () at each iteration, it will receive three arguments: currentValue , currentIndex and the full array . This is why three entries are output at each iteration.
Now we have everything we need to solve the mystery.
Together
ParseInt takes two arguments: string and radix (base). If the transmitted radix is false, then the default is 10.
parseInt('11'); => 11
parseInt('11', 2); => 3
parseInt('11', 16); => 17
parseInt('11', undefined); => 11 (radix ложен)
parseInt('11', 0); => 11 (radix ложен)
Let's look at this example step by step.
['1', '7', '11'].map(parseInt); => [1, NaN, 3]
// Первая итерация: val = '1', index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']); => 1
Since 0 is false, the default value for the base is set to 10 . parseInt () accepts only two arguments, so the third argument ['1', '7', '11'] is ignored. Line '1' in base 10 will give result 1 .
// Вторая итерация: val = '7', index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']); => NaN
In the base 1 system, the character '7' does not exist. As with the first iteration, the last argument is ignored. Thus parseInt () returns NaN .
// Третья итерация: val = '11', index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']); => 3
In binary notation, '11' refers to the number 3 . The last argument is again ignored.
Итог (TL;DR)
['1', '7', '11'].map(parseInt) не работает как было задумано, потому что map передает три аргумента в parseInt() на каждой итерации. Второй аргумент index передается в parseInt в качестве параметра radix (основание системы счисления). Таким образом, каждая строка в массиве анализируется с использованием недефолтного основания. '7' анализируется по основанию 1, что даёт NaN; '11' анализируется как двоичное число — итог 3. '1' анализируется по дефолтному основанию 10, потому что его индекс 0 is false.
And here is the code that will work the way we wanted:
['1', '7', '11'].map(numStr => parseInt(numStr));