Чому ['1', '7', '11'].map(parseInt) повертає [1, NaN, 3] в JavaScript

8 хв. читання

JavaScript — дивний. Не вірите? Тоді спробуйте перетворити масив рядків у цілочислові значення, використовуючи map та parseInt. Запустіть вашу консоль (F12 для Chrome), вставте наведений нижче код та натисніть Enter.

['1', '7', '11'].map(parseInt);

Замість того щоб отримати масив цілих чисел [1, 7, 11], ми бачимо [1, NaN, 3]. Неочікувано? Щоб розібратися, що ж насправді тут відбувається, оглянемо спершу деякі концепції JavaScript. Якщо ж вам потрібен TLDR — він наприкінці матеріалу.

Правдиві та хибні значення

Поглянемо на добре знайомий умовний вираз в JavaScript:

if (true) {
    // цей код завжди виконується
} else {
    // ніколи не виконається
}

У цьому прикладі умова оператора завжди істинна, тому if-блок завжди виконується, а else-блок ігнорується. Ми розглянули лише найпростіший приклад, адже true — булеве значення. Як йдуть справи зі значеннями інших типів?

if ("hello world") {
    // це запуститься?
    console.log("Condition is truthy");
} else {
    // чи це?
    console.log("Condition is falsy");
}

Знову звернемося до консолі розробника (F12 для Chrome). Побачимо, що виконується if-блок. Така поведінка пояснюється тим, що рядок "hello world" сприймається як істинне значення.

Кожен об'єкт в JavaScript є truthly або falsy. Якщо об'єкт трапляється в булевому контексті, на зразок умовного виразу, він розглядається як true чи false залежно від його «істинності». Як саме визначається «істинність»? Є просте правило:

Усі значення є істинними, за винятком: false, 0, "" (пустий рядок), null, undefined та NaN.

Тож нехай вас не заплутує те, що "false", рядок "0", пустий об'єкт {} та пустий масив [] є істинними значеннями. Ви можете переконатися в цьому, передавши об'єкт як аргумент функції Boolean (тобто Boolean("0")).

Нам досить переконатися, що 0 — еквівалентно false.

Основа системи числення

0 1 2 3 4 5 6 7 8 9 10

Коли ми рахуємо від 0 до 9, то використовуємо різні символи для кожного числа (0–9). Коли ж ми доходимо до 10, нам вже потрібно два різні символи (1 та 0), щоб відтворити число. Усе тому, що основою десяткової системи числення є 10.

Основа — найменше число, яке може бути представлене більше ніж одним символом. Всі системи числення відрізняються основами, саме тому ті самі символи позначають різні цифри.

ДЕСЯТКОВА ДВІЙКОВА  ШІСТНАДЦЯТКОВА
ОСНОВА=10 ОСНОВА=2  ОСНОВА=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

Для прикладу розглянемо наведену таблицю і побачимо, що символ 11 представляє різні числа в кожній системі числення. Для двійкової системи — це 3, а якщо основа системи числення 16 — то 17.

Ви могли помітити, що в нашому прикладі parseInt повернув 3, коли аргументом було число 11. Тобто ми отримали число 3 у двійковому форматі, відповідно до наведеної таблиці.

Аргументи функцій

Функції в JavaScript можна викликати з будь-якою кількістю аргументів, навіть якщо ця кількість перевищує задану. Пропущені аргументи отримують значення undefined, а зайві — ігноруються (проте зберігаються в масивоподібному об'єкті args).

function foo(x, y) {
    console.log(x);
    console.log(y);
}
foo(1, 2);      // logs 1, 2
foo(1);         // logs 1, undefined
foo(1, 2, 3);   // logs 1, 2

map

Ми вже майже досягли мети!

Map — метод Array.prototype, що повертає новий масив, кожен елемент якого обробляється визначеною функцією. Наприклад, цей фрагмент коду збільшує кожен елемент масиву в 3 рази:

function multiplyBy3(x) {
    return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result);   // logs [3, 6, 9, 12, 15];

Скажімо, я хочу вивести кожен елемент, використовуючи map() (без виразу return). Тож мені потрібно передати console.log як аргумент, чи не так?

[1, 2, 3, 4, 5].map(console.log);
Чому ['1', '7', '11'].map(parseInt) повертає [1, NaN, 3] в JavaScript

Проте відбувається щось дивне. Замість того щоб просто вивести одне значення, на виході отримуємо також індекс та повний масив.

[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)
);

Коли ми передаємо функцію в map(), на кожній ітерації вона отримує три аргументи: currentValue, currentIndex та повний array. Ось чому результат відрізняється від очікуваного.

У нас вже є все, щоб розгадати цю загадку.

Складаємо все докупи

ParseInt приймає два аргументи: string та radix. Якщо значення radix хибне, за замовчуванням встановлюється 10.

parseInt('11');                => 11
parseInt('11', 2);             => 3
parseInt('11', 16);            => 17
parseInt('11', undefined);     => 11 (radix є falsy)
parseInt('11', 0);             => 11 (radix є falsy)

Тепер проженемо наш приклад крок за кроком:

['1', '7', '11'].map(parseInt);       => [1, NaN, 3]
// Перша ітерація: val = '1', index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']);   => 1

Оскільки 0 стосується хибних значень, основа системи числення встановлюється як 10. parseInt() приймає лише два аргументи, тому масив ['1', '7', '11'] ігнорується. Символу 1 в десятковій системі числення відповідає число 1.

// Друга ітерація: val = '7', index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']);   => NaN

В системі числення з основою 1 числа 7 не існує. Як і в попередній ітерації, останній аргумент ігнорується. Саме тому parseInt() повертає NaN.

// Третя ітерація: val = '11', index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']);   => 3

У двійковій системі числення символ 11 відповідає числу 3. Останній аргумент, як завжди, ігноруємо.

Підсумок (TLDR)

['1', '7', '11'].map(parseInt) поводить себе неочікувано, тому що map передає три аргументи в parseInt() на кожній ітерації. Другий аргумент, index, передається як параметр radix. Тож кожен елемент масиву передається з різними значеннями основи системи числення, що відповідає індексу елемента: '7' — з основою 1, тобто отримуємо на виході NaN; '11' — з основою 2, тому як результат отримуємо 3. '1' використовує десяткову основу за замовчуванням, оскільки значення індексу 0 є хибним.

І наостанок — код, з яким отримаємо очікуваний результат:

['1', '7', '11'].map(numStr => parseInt(numStr));
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 4.7K
Приєднався: 10 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація