5 способов вернуть несколько значений из функции в JavaScript
Для кого эта статья:
- Опытные разработчики JavaScript, стремящиеся улучшить код и освоить новые техники.
- Студенты и начинающие программисты, обучающиеся веб-разработке и ищущие практические примеры.
Лица, заинтересованные в оптимизации работы с данными и улучшении читаемости кода.
Каждый разработчик JavaScript сталкивается с ситуацией, когда функция должна вернуть больше одного значения. Казалось бы, тривиальная задача превращается в головоломку: как организовать передачу данных без дополнительных переменных? Как избежать громоздкого кода и сохранить читаемость? Существует несколько элегантных подходов, о которых многие программисты не знают или используют неэффективно. Готовы переосмыслить свой код и освоить пять мощных техник возврата нескольких значений? 🚀
Изучение эффективных способов возврата нескольких значений – одна из базовых, но критически важных тем в JavaScript. На курсе Обучение веб-разработке от Skypro мы не просто показываем синтаксис, а учим мыслить как профессиональный разработчик. Вы освоите не только различные методы работы с данными, но и поймёте, когда какой метод применять для максимальной производительности и читаемости кода. Ваши функции станут гибкими, элегантными и готовыми к масштабированию.
Возвращение нескольких значений: проблемы и решения
В классических языках программирования функция может вернуть только одно значение. JavaScript следует этому правилу, но предлагает гибкие механизмы для обхода данного ограничения. Рассмотрим типичную ситуацию: функция должна вернуть результат вычисления вместе с дополнительными метаданными — временем выполнения, статусом операции или промежуточными значениями.
Александр Петров, Lead JavaScript-разработчик
Как-то раз я разрабатывал систему аналитики, которая обрабатывала огромные объёмы данных. Одна функция должна была не только возвращать обработанные метрики, но и информацию о времени выполнения, количестве обработанных записей и статусе операции. Сначала я использовал глобальные переменные для хранения этих дополнительных данных — решение работало, но код становился запутанным и плохо тестируемым.
Когда я перешёл на возврат объекта с именованными свойствами, код стал намного чище. А после внедрения деструктуризации время на поддержку кода сократилось на 30%. Теперь, когда я проводу код-ревью, первое, на что обращаю внимание — как разработчик организует возврат нескольких значений из функции.
Существует пять основных подходов к возврату множественных значений, каждый со своими преимуществами и случаями применения:
- Массивы с деструктуризацией — компактный способ получить несколько значений одной строкой
- Объектные литералы — именованный доступ к результатам без привязки к порядку
- Замыкания — функции, сохраняющие и предоставляющие доступ к нескольким результатам
- Мемоизация — кеширование результатов вычислений для повторного использования
- Map и современный синтаксис — работа со сложными структурами данных
| Метод | Синтаксическая сложность | Читаемость | Масштабируемость |
|---|---|---|---|
| Массивы с деструктуризацией | Низкая | Средняя | Низкая |
| Объектные литералы | Низкая | Высокая | Высокая |
| Замыкания | Средняя | Средняя | Средняя |
| Мемоизация | Высокая | Средняя | Высокая |
| Map и современный синтаксис | Средняя | Высокая | Высокая |
Выбор подхода зависит от контекста задачи, требований к читаемости кода и предпочтений команды разработчиков. Давайте рассмотрим каждый метод подробнее. 🧩

Возврат массивов с деструктуризацией в JavaScript
Простейший способ вернуть несколько значений из функции — использовать массив. Этот подход особенно эффективен в сочетании с деструктуризацией, которая позволяет элегантно извлекать отдельные элементы массива в переменные.
Рассмотрим базовый пример функции, возвращающей статистику по тексту:
function analyzeText(text) {
const charCount = text.length;
const wordCount = text.split(/\s+/).filter(word => word.length > 0).length;
const lineCount = text.split('\n').length;
return [charCount, wordCount, lineCount];
}
// Использование с деструктуризацией
const [chars, words, lines] = analyzeText("Hello world!\nThis is a test.");
console.log(`Символов: ${chars}, Слов: ${words}, Строк: ${lines}`);
Преимущество этого подхода заключается в его лаконичности. Однако при большом количестве возвращаемых значений или при изменении их порядка могут возникнуть проблемы с сопровождением кода. 🔄
Деструктуризация также позволяет пропускать ненужные значения и задавать значения по умолчанию:
// Получаем только первое и третье значение
const [charCount, , lineCount] = analyzeText(text);
// Значение по умолчанию, если массив короткий
const [count, total = 0] = getSomeValues();
Для функций, которые могут возвращать разное количество значений в зависимости от условий, можно использовать оператор rest:
function processData(input) {
// Логика обработки
return [result, ...additionalInfo];
}
const [mainResult, ...details] = processData(data);
Однако у массивов есть существенный недостаток — зависимость от позиции элемента. Если функция будет модифицирована и порядок возвращаемых значений изменится, вызывающий код может получить некорректные данные без явных ошибок. Поэтому для более сложных случаев стоит рассмотреть другие подходы. 🚫
Объектные литералы: именованный доступ к результатам
Когда порядок или количество возвращаемых значений может меняться, объектные литералы предоставляют более надёжное решение. Они позволяют получать доступ к результатам по именам свойств, а не по позициям, что делает код более устойчивым к изменениям и самодокументируемым.
function analyzeText(text) {
return {
charCount: text.length,
wordCount: text.split(/\s+/).filter(word => word.length > 0).length,
lineCount: text.split('\n').length,
isEmpty: text.trim().length === 0
};
}
const stats = analyzeText("Hello world!\nThis is a test.");
console.log(`Символов: ${stats.charCount}, Слов: ${stats.wordCount}`);
// С деструктуризацией объекта
const { charCount, wordCount, isEmpty } = analyzeText(text);
Объектные литералы обладают рядом преимуществ при возврате множественных значений:
- Самодокументируемость — имена свойств объясняют назначение каждого значения
- Гибкость — можно добавлять новые свойства без нарушения существующего кода
- Выборочное использование — клиентский код может использовать только нужные ему значения
- Типобезопасность — в TypeScript можно задать точный интерфейс возвращаемого объекта
Современный JavaScript позволяет использовать сокращённую запись свойств, если имя переменной совпадает с именем свойства:
function calculateCircle(radius) {
const diameter = radius * 2;
const circumference = 2 * Math.PI * radius;
const area = Math.PI * radius * radius;
return { diameter, circumference, area };
// Эквивалентно: return { diameter: diameter, circumference: circumference, area: area };
}
Елена Соколова, JavaScript-тренер
Работая техническим консультантом в финтех-проекте, я столкнулась с кодовой базой, где разработчики активно использовали массивы для возврата множественных значений из функций. Сначала это казалось удобным, но по мере роста проекта начались проблемы — при добавлении новых данных в возвращаемый результат приходилось изменять все вызовы функции.
Я предложила стандартизировать подход, используя объектные литералы. Провели рефакторинг ключевых модулей, и число ошибок при разработке новых функций снизилось на 40%. Особенно ценным оказалось то, что при добавлении новых свойств в возвращаемый объект существующий код продолжал работать без изменений.
Любопытно, что после этой стандартизации скорость онбординга новых разработчиков тоже увеличилась — код стал более самоописательным и предсказуемым.
Деструктуризация объектов также позволяет переименовывать свойства и задавать значения по умолчанию:
// Переименование при деструктуризации
const { charCount: chars, wordCount: words, nonExistentProp: missing = 'N/A' } = stats;
При работе с асинхронным кодом объектные литералы особенно полезны для передачи результатов и метаданных:
async function fetchUserData(userId) {
try {
const startTime = Date.now();
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
return {
user: userData,
status: response.status,
executionTime: Date.now() – startTime,
success: response.ok
};
} catch (error) {
return {
user: null,
status: 0,
executionTime: Date.now() – startTime,
success: false,
error: error.message
};
}
}
| Характеристика | Массивы | Объекты |
|---|---|---|
| Доступ к данным | По индексу/позиции | По имени свойства |
| Читаемость | Низкая без комментариев | Высокая (самодокументирующийся код) |
| Устойчивость к изменениям | Низкая (зависит от порядка) | Высокая (независимость от порядка) |
| Синтаксическая краткость | Высокая | Средняя |
| Типизация (в TypeScript) | Возможна, но менее явная | Более строгая и описательная |
Объектные литералы — предпочтительный способ возврата множественных значений для большинства случаев в JavaScript, особенно когда читаемость и масштабируемость кода являются приоритетом. 📝
Замыкания и мемоизация для работы с множественными значениями
Замыкания предлагают нестандартный, но мощный подход к возврату нескольких значений, особенно когда требуется сохранять состояние между вызовами или предоставлять контролируемый доступ к результатам. Вместо одноразового возврата всех значений функция возвращает другую функцию (или объект с методами), которая имеет доступ к вычисленным значениям.
Рассмотрим пример функции, которая выполняет дорогостоящие вычисления и предоставляет доступ к различным результатам:
function createDataProcessor(data) {
// Выполняем начальные вычисления
const processedData = heavyComputation(data);
const summary = generateSummary(processedData);
let accessCount = 0;
// Возвращаем объект с методами
return {
getData() {
accessCount++;
return processedData;
},
getSummary() {
accessCount++;
return summary;
},
getAccessStats() {
return {
totalAccesses: accessCount,
lastAccess: new Date()
};
}
};
}
// Использование
const processor = createDataProcessor(largeDataset);
const summary = processor.getSummary();
console.log(`Количество обращений: ${processor.getAccessStats().totalAccesses}`);
Такой подход имеет несколько преимуществ:
- Данные вычисляются один раз, но могут использоваться многократно
- Можно контролировать доступ к отдельным результатам
- Легко добавлять метаданные и статистику использования
- Обеспечивает инкапсуляцию — внутренние данные защищены
Мемоизация расширяет концепцию замыканий, добавляя кеширование результатов функции. Это особенно полезно, когда функция возвращает несколько значений и её вычисление затратно:
function memoizedCalculator() {
const cache = new Map();
return function(a, b) {
const key = `${a},${b}`;
if (cache.has(key)) {
console.log("Returning from cache");
return cache.get(key);
}
console.log("Calculating...");
// Тяжелые вычисления
const sum = a + b;
const product = a * b;
const ratio = a / b;
const result = { sum, product, ratio };
cache.set(key, result);
return result;
};
}
const calculate = memoizedCalculator();
console.log(calculate(4, 2)); // Вычисляет
console.log(calculate(4, 2)); // Берёт из кеша
Для более сложных случаев мемоизация может быть расширена для работы с асинхронными функциями:
function createAsyncProcessor() {
const cache = new Map();
return async function process(id) {
if (cache.has(id)) {
return {
...cache.get(id),
fromCache: true
};
}
const data = await fetchDataFromAPI(id);
const processed = heavyProcessing(data);
const result = {
raw: data,
processed: processed,
timestamp: Date.now()
};
cache.set(id, result);
return { ...result, fromCache: false };
};
}
const processor = createAsyncProcessor();
Замыкания с мемоизацией особенно эффективны, когда нужно:
- Избежать повторных дорогостоящих вычислений
- Возвращать разные наборы данных в зависимости от контекста
- Сохранять историю вызовов и промежуточные результаты
- Реализовывать ленивые вычисления (выполнять только по требованию)
Этот подход идеален для создания API с множественными точками доступа к данным, особенно когда эти данные связаны и их вычисление требует значительных ресурсов. 🧠
Применение класса Map и современного синтаксиса для сложных данных
Для более сложных случаев, когда требуется вернуть структурированные данные с возможностью эффективного поиска, класс Map и другие современные структуры данных JavaScript предоставляют дополнительные возможности.
Map имеет несколько преимуществ перед обычными объектами при возврате множественных значений:
- Ключами могут быть любые типы данных, включая объекты и функции
- Гарантированный порядок элементов (в порядке добавления)
- Встроенный API для удобной работы (size, has(), get(), set(), delete())
- Лучшая производительность при частом добавлении/удалении элементов
Рассмотрим функцию, которая анализирует текст и возвращает Map с различными статистиками:
function analyzeTextExtended(text) {
const result = new Map();
// Базовые метрики
result.set('charCount', text.length);
result.set('wordCount', text.split(/\s+/).filter(word => word.length > 0).length);
result.set('lineCount', text.split('\n').length);
// Частотный анализ
const wordFrequency = new Map();
text.split(/\s+/).filter(word => word.length > 0).forEach(word => {
const normalized = word.toLowerCase();
wordFrequency.set(normalized, (wordFrequency.get(normalized) || 0) + 1);
});
result.set('wordFrequency', wordFrequency);
// Добавляем функции для дальнейшего анализа
result.set('getMostFrequentWord', function() {
let maxWord = '';
let maxCount = 0;
for (const [word, count] of wordFrequency) {
if (count > maxCount) {
maxWord = word;
maxCount = count;
}
}
return { word: maxWord, count: maxCount };
});
return result;
}
// Использование
const textAnalysis = analyzeTextExtended("Hello world! Hello JavaScript. This is a test.");
console.log(`Символов: ${textAnalysis.get('charCount')}`);
console.log(`Наиболее частое слово: ${textAnalysis.get('getMostFrequentWord')().word}`);
// Деструктуризация с Map
const { charCount, wordCount } = Object.fromEntries(textAnalysis);
Комбинируя Map с другими структурами данных и современным синтаксисом ES6+, можно создавать гибкие решения для возврата сложных наборов данных:
function processComplexData(data) {
// Основные результаты
const results = new Map();
// Различные представления данных
const summary = {};
const errors = [];
const warnings = [];
// Обработка данных и заполнение структур
// ...
// Возвращаем комбинированный объект с Map и другими структурами
return {
getResults: () => results,
getSummary: () => summary,
hasErrors: () => errors.length > 0,
getErrors: () => [...errors],
getWarnings: () => [...warnings],
// Возвращаем итерируемый объект для деструктуризации
[Symbol.iterator]: function* () {
yield results;
yield summary;
yield errors.length > 0;
}
};
}
// Использование
const processed = processComplexData(someData);
// Стандартный доступ
const results = processed.getResults();
if (processed.hasErrors()) {
console.error(processed.getErrors());
}
// С использованием итератора
const [resultsMap, summaryObj, hasErrors] = processed;
Set, WeakMap и WeakSet также могут использоваться для специфических случаев возврата данных:
- Set — для возврата уникальных значений без дублирования
- WeakMap — когда ключами являются объекты с неопределенным временем жизни
- WeakSet — для хранения уникальных объектов без предотвращения сборки мусора
Современный JavaScript также предоставляет возможность использовать генераторы для поэтапного возврата нескольких значений:
function* dataProcessor(input) {
// Первый этап обработки
const step1Result = processStep1(input);
yield { step: 1, result: step1Result };
// Второй этап
const step2Result = processStep2(step1Result);
yield { step: 2, result: step2Result };
// Финальный результат
const finalResult = finalize(step2Result);
yield { step: 'final', result: finalResult };
}
// Использование
const processor = dataProcessor(data);
const { result: step1 } = processor.next().value;
const { result: step2 } = processor.next().value;
const { result: final } = processor.next().value;
Такой подход позволяет не только возвращать множественные значения, но и контролировать процесс их получения, что идеально для сложных алгоритмов обработки данных или пошаговых вычислений. 🧮
Возврат нескольких значений из функции — это не просто технический трюк, а важный архитектурный выбор, влияющий на читаемость, тестируемость и масштабируемость кода. Выбирая между массивами, объектами, замыканиями, Map и другими структурами, опирайтесь на контекст задачи: используйте массивы для простых случаев, объекты для семантически связанных данных, замыкания для сохранения состояния, Map для сложных структур с нестандартными ключами. Комбинируя эти подходы с современным синтаксисом JavaScript, вы сможете создавать код, который не только решает текущие задачи, но и легко адаптируется к новым требованиям.