> Как избежать конкатенации чисел как строк в функции суммы аргументов в JavaScript? (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Газпромбанк

Стек: JavaScript

> Пример ответа

Короткий ответ

Проблема возникает из-за неявного приведения типов в JavaScript: оператор + выполняет конкатенацию строк, если хотя бы один операнд - строка. Решение - явно приводить аргументы к числам перед сложением, например через Number(), унарный +, parseInt/parseFloat, или использовать reduce с начальным числовым значением. Также можно проверять типы аргументов и выбрасывать ошибку при нечисловых значениях.

Подробное объяснение

В JavaScript оператор + перегружен: для чисел он выполняет сложение, для строк - конкатенацию. Если в функцию суммы передаются аргументы разных типов, например sum(1, '2'), результат будет '12' (строка), а не 3. Это классическая проблема неявного приведения типов.

Основные подходы к решению:

  1. Явное приведение каждого аргумента к числу - гарантирует, что все операнды будут числами перед сложением.
  2. Валидация типов - проверка, что все аргументы являются числами, с выбрасыванием ошибки при несоответствии.
  3. Использование BigInt - если нужна работа с большими целыми числами без потери точности.
  4. Использование Number.EPSILON - для корректной обработки чисел с плавающей точкой.

Важно учитывать edge cases: null приводится к 0, undefined - к NaN, строки вида ' 5 ' (с пробелами) корректно парсятся, а '5abc' даёт NaN.

На практике

В реальных проектах чаще всего используют комбинацию явного приведения и валидации. Для production-кода рекомендуется:

  • Использовать Number() или унарный + для приведения.
  • Добавить проверку на NaN через Number.isNaN().
  • Для пользовательского ввода дополнительно применять parseFloat с проверкой результата.
  • В TypeScript - использовать строгую типизацию параметров.

Пример типичной реализации в библиотеке утилит:

JAVASCRIPT
function safeSum(...args) {
return args.reduce((acc, val) => {
const num = Number(val);
if (Number.isNaN(num)) {
throw new TypeError(`Invalid number: ${val}`);
}
return acc + num;
}, 0);
}

Пример кода

JAVASCRIPT
// Проблемный вариант
function sumBad(...args) {
return args.reduce((acc, val) => acc + val, 0);
}
console.log(sumBad(1, '2')); // '12' - конкатенация
// Решение 1: явное приведение
function sumWithNumber(...args) {
return args.reduce((acc, val) => acc + Number(val), 0);
}
console.log(sumWithNumber(1, '2')); // 3
// Решение 2: унарный плюс
function sumWithUnary(...args) {
return args.reduce((acc, val) => acc + +val, 0);
}
console.log(sumWithUnary(1, '2')); // 3
// Решение 3: с валидацией
function sumSafe(...args) {
return args.reduce((acc, val) => {
const num = Number(val);
if (!Number.isFinite(num)) {
throw new Error(`Argument "${val}" is not a finite number`);
}
return acc + num;
}, 0);
}
// Решение 4: для целых чисел с BigInt
function sumBigInt(...args) {
return args.reduce((acc, val) => acc + BigInt(val), 0n);
}

Как отвечать на собеседовании

Начни с чёткого описания проблемы: неявное приведение типов в операторе +. Затем перечисли основные решения, начиная с самого простого (явное приведение) и заканчивая более надёжными (с валидацией). Обязательно упомяни edge cases: null, undefined, NaN, строки с пробелами и буквами. Покажи понимание разницы между Number(), parseInt и parseFloat. Если спросят про производительность - отметь, что унарный + обычно быстрее Number(), но разница незначительна. Хорошо добавить про TypeScript как способ предотвратить проблему на этапе компиляции.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание неявного приведения типов в JavaScript.
  • Знание различных способов преобразования в число и их особенностей.
  • Умение обрабатывать edge cases и писать надёжный код.
  • Способность выбрать подходящее решение для конкретного контекста (простота vs надёжность).
  • Понимание разницы между Number(), parseInt, parseFloat и унарным +.
  • Знание Number.isNaN vs глобальной isNaN.

Типичные ошибки

  • Использование parseInt без указания системы счисления (radix) - может дать неожиданные результаты для строк вида '0x10'.
  • Забывают про NaN как результат приведения - нужно всегда проверять.
  • Путают Number.isNaN() (не приводит к числу) с глобальной isNaN() (приводит).
  • Используют + для конкатенации строк в других частях кода, что маскирует проблему.
  • Не учитывают, что null приводится к 0, а undefined - к NaN.
  • Применяют parseFloat для целых чисел, хотя parseInt точнее для этой задачи.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью