Функции — это основа программирования и один из важнейших инструментов в JavaScript. Они решают фундаментальные проблемы разработки:
Без функций код превращается в “спагетти” — запутанную массу инструкций, которую невозможно поддерживать.
Проблема без функций:
// Плохо: дублирование кода
let user1Name = "Иван";
let user1Email = "ivan@example.com";
console.log("Привет, " + user1Name + "!");
console.log("Ваш email: " + user1Email);
let user2Name = "Мария";
let user2Email = "maria@example.com";
console.log("Привет, " + user2Name + "!");
console.log("Ваш email: " + user2Email);
let user3Name = "Петр";
let user3Email = "petr@example.com";
console.log("Привет, " + user3Name + "!");
console.log("Ваш email: " + user3Email);
Решение с функциями:
// Хорошо: переиспользуемая функция
function greetUser(name, email) {
console.log(`Привет, ${name}!`);
console.log(`Ваш email: ${email}`);
}
greetUser("Иван", "ivan@example.com");
greetUser("Мария", "maria@example.com");
greetUser("Петр", "petr@example.com");
Проблема:
// Плохо: всё в одном месте
let price = 1000;
let discount = 0.1;
let tax = 0.18;
let shipping = 200;
let discountAmount = price * discount;
let priceAfterDiscount = price - discountAmount;
let taxAmount = priceAfterDiscount * tax;
let finalPrice = priceAfterDiscount + taxAmount + shipping;
console.log("Итоговая цена: " + finalPrice);
Решение:
// Хорошо: разбито на логические блоки
function calculateDiscount(price, discountRate) {
return price * discountRate;
}
function calculateTax(amount, taxRate) {
return amount * taxRate;
}
function calculateFinalPrice(price, discount, tax, shipping) {
const discountAmount = calculateDiscount(price, discount);
const priceAfterDiscount = price - discountAmount;
const taxAmount = calculateTax(priceAfterDiscount, tax);
return priceAfterDiscount + taxAmount + shipping;
}
const finalPrice = calculateFinalPrice(1000, 0.1, 0.18, 200);
console.log(`Итоговая цена: ${finalPrice}`);
Проблема:
// Плохо: глобальные переменные
var counter = 0;
var step = 1;
// Где-то в коде
counter += step;
// В другом месте кто-то случайно изменил
step = 10;
// Теперь счетчик работает неправильно
counter += step; // +10 вместо +1
Решение:
// Хорошо: инкапсуляция в функции
function createCounter(initialValue = 0, stepValue = 1) {
let counter = initialValue;
let step = stepValue;
return {
increment() {
counter += step;
return counter;
},
decrement() {
counter -= step;
return counter;
},
getValue() {
return counter;
}
};
}
const myCounter = createCounter();
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
Назначение: Основной способ создания переиспользуемых блоков кода
function calculateArea(width, height) {
return width * height;
}
// Доступна до объявления благодаря hoisting
console.log(getGreeting("Мир")); // "Привет, Мир!"
function getGreeting(name) {
return `Привет, ${name}!`;
}
Назначение: Создание функций как значений, условное объявление
// Присваивание функции переменной
const multiply = function(a, b) {
return a * b;
};
// Условное создание функции
let operation;
if (userPreference === 'add') {
operation = function(a, b) { return a + b; };
} else {
operation = function(a, b) { return a - b; };
}
Назначение: Краткий синтаксис, особенно для коллбэков и функциональных методов
// Краткая запись
const square = x => x * x;
// Отлично для массивов
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// Сохраняют контекст this
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++; // this указывает на экземпляр Timer
console.log(this.seconds);
}, 1000);
}
}
Назначение: Функции, связанные с данными объекта
const user = {
name: "Анна",
age: 25,
// Метод объекта
introduce() {
return `Меня зовут ${this.name}, мне ${this.age} лет`;
},
// Метод с логикой
canVote() {
return this.age >= 18;
},
// Метод изменения состояния
celebrateBirthday() {
this.age++;
console.log(`С днем рождения! Теперь мне ${this.age}`);
}
};
// Валидация данных
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Форматирование
function formatCurrency(amount, currency = 'RUB') {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: currency
}).format(amount);
}
// Трансформация данных
function normalizeUserData(rawUser) {
return {
id: rawUser.user_id,
name: rawUser.full_name?.trim(),
email: rawUser.email_address?.toLowerCase(),
isActive: rawUser.status === 'active'
};
}
// Создание элементов
function createElement(tag, className, textContent) {
const element = document.createElement(tag);
if (className) element.className = className;
if (textContent) element.textContent = textContent;
return element;
}
// Обработка событий
function handleFormSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const userData = Object.fromEntries(formData);
if (validateUserData(userData)) {
submitUserData(userData);
} else {
showValidationErrors(userData);
}
}
// Анимации
function fadeIn(element, duration = 300) {
element.style.opacity = 0;
element.style.display = 'block';
const start = performance.now();
function animate(currentTime) {
const elapsed = currentTime - start;
const progress = Math.min(elapsed / duration, 1);
element.style.opacity = progress;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Работа с API
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
return normalizeUserData(userData);
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
throw error;
}
}
// Дебаунсинг
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Использование
const debouncedSearch = debounce(function(query) {
searchAPI(query);
}, 300);
Функции высшего порядка — функции, которые:
// Функция принимает другую функцию
function processArray(array, processor) {
const result = [];
for (let item of array) {
result.push(processor(item));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const squared = processArray(numbers, x => x * x);
const doubled = processArray(numbers, x => x * 2);
// Функция возвращает другую функцию
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Композиция функций
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;
const addOneThenDouble = compose(multiplyByTwo, addOne);
console.log(addOneThenDouble(3)); // 8 (3 + 1) * 2
const Calculator = (function() {
// Приватные переменные
let history = [];
// Приватные функции
function addToHistory(operation, result) {
history.push({ operation, result, timestamp: Date.now() });
}
// Публичный API
return {
add(a, b) {
const result = a + b;
addToHistory(`${a} + ${b}`, result);
return result;
},
subtract(a, b) {
const result = a - b;
addToHistory(`${a} - ${b}`, result);
return result;
},
getHistory() {
return [...history]; // Возвращаем копию
},
clearHistory() {
history = [];
}
};
})();
function createUser(name, email, role = 'user') {
return {
name,
email,
role,
// Методы пользователя
getDisplayName() {
return this.name;
},
hasPermission(permission) {
const permissions = {
user: ['read'],
admin: ['read', 'write', 'delete'],
moderator: ['read', 'write']
};
return permissions[this.role]?.includes(permission) || false;
},
updateProfile(newData) {
Object.assign(this, newData);
}
};
}
// Использование
const user = createUser("Анна", "anna@example.com");
const admin = createUser("Иван", "ivan@example.com", "admin");
// Базовая функция
function greet(name) {
return `Привет, ${name}!`;
}
// Декораторы
function withLogging(func) {
return function(...args) {
console.log(`Вызов функции с аргументами:`, args);
const result = func.apply(this, args);
console.log(`Результат:`, result);
return result;
};
}
function withTiming(func) {
return function(...args) {
const start = performance.now();
const result = func.apply(this, args);
const end = performance.now();
console.log(`Выполнение заняло ${end - start} мс`);
return result;
};
}
// Применение декораторов
const decoratedGreet = withTiming(withLogging(greet));
decoratedGreet("Мир");
Подход | Преимущества | Недостатки | Когда использовать |
---|---|---|---|
Без функций | Простота для новичков | Дублирование, сложность поддержки | Только для обучения |
Обычные функции | Hoisting, читаемость | Больше кода | Основная логика приложения |
Стрелочные функции | Краткость, лексический this | Нет hoisting, нет arguments | Коллбэки, функциональное программирование |
Методы классов | ООП подход, инкапсуляция | Сложность для простых задач | Сложные объекты с состоянием |
Функции высшего порядка | Гибкость, переиспользование | Сложность понимания | Библиотеки, утилиты |
// ❌ Плохо: функция делает слишком много
function processUser(userData) {
// Валидация
if (!userData.email || !userData.name) {
throw new Error('Неполные данные');
}
// Форматирование
userData.email = userData.email.toLowerCase();
userData.name = userData.name.trim();
// Сохранение в базу
database.save(userData);
// Отправка email
emailService.sendWelcome(userData.email);
// Логирование
logger.log(`Пользователь ${userData.name} создан`);
}
// ✅ Хорошо: каждая функция решает одну задачу
function validateUserData(userData) {
if (!userData.email || !userData.name) {
throw new Error('Неполные данные');
}
}
function formatUserData(userData) {
return {
...userData,
email: userData.email.toLowerCase(),
name: userData.name.trim()
};
}
function saveUser(userData) {
return database.save(userData);
}
function sendWelcomeEmail(email) {
return emailService.sendWelcome(email);
}
function logUserCreation(name) {
logger.log(`Пользователь ${name} создан`);
}
// Композиция функций
async function createUser(userData) {
validateUserData(userData);
const formattedData = formatUserData(userData);
const savedUser = await saveUser(formattedData);
await sendWelcomeEmail(savedUser.email);
logUserCreation(savedUser.name);
return savedUser;
}
// ❌ Плохо: функция с побочными эффектами
let counter = 0;
function incrementAndGet() {
counter++; // Изменяет внешнее состояние
console.log(counter); // Побочный эффект
return counter;
}
// ✅ Хорошо: чистая функция
function increment(value) {
return value + 1; // Только возвращает результат
}
// ✅ Хорошо: чистая функция для массивов
function addItem(array, item) {
return [...array, item]; // Не изменяет исходный массив
}
// ✅ Хорошо: чистая функция для объектов
function updateUser(user, updates) {
return { ...user, ...updates }; // Возвращает новый объект
}
// ❌ Плохо: неясные имена
function calc(a, b, c) {
return a * b * c / 100;
}
function proc(data) {
return data.filter(x => x.active).map(x => x.name);
}
// ✅ Хорошо: описательные имена
function calculateTotalPrice(price, quantity, taxRate) {
return price * quantity * taxRate / 100;
}
function getActiveUserNames(users) {
return users
.filter(user => user.active)
.map(user => user.name);
}
// ✅ Хорошо: функции-предикаты
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function hasPermission(user, permission) {
return user.permissions.includes(permission);
}
// ✅ Хорошо: явная обработка ошибок
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('Ошибка парсинга JSON:', error.message);
return null;
}
}
// ✅ Хорошо: валидация параметров
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Аргументы должны быть числами');
}
if (b === 0) {
throw new Error('Деление на ноль невозможно');
}
return a / b;
}
// ✅ Хорошо: async/await с обработкой ошибок
async function fetchUserSafely(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`Пользователь не найден: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
return null;
}
}
❌ Проблема: Функции, которые делают слишком много
Признаки:
✅ Решение: Разбивайте на более мелкие функции
// ❌ Плохо: потеря контекста
const user = {
name: "Анна",
greet() {
return `Привет, я ${this.name}`;
}
};
const greetFunction = user.greet;
console.log(greetFunction()); // "Привет, я undefined"
// ✅ Хорошо: сохранение контекста
const boundGreet = user.greet.bind(user);
console.log(boundGreet()); // "Привет, я Анна"
// ✅ Или используйте стрелочные функции
const user2 = {
name: "Петр",
greet: () => `Привет, я ${this.name}`, // this не работает!
greetCorrect() {
return `Привет, я ${this.name}`; // this работает
}
};
// ❌ Плохо: изменение входных данных
function addItem(array, item) {
array.push(item); // Изменяет исходный массив!
return array;
}
function updateUser(user, newData) {
user.name = newData.name; // Изменяет исходный объект!
return user;
}
// ✅ Хорошо: создание новых данных
function addItemSafe(array, item) {
return [...array, item];
}
function updateUserSafe(user, newData) {
return { ...user, ...newData };
}
// ✅ Современный подход
function createUser({ name, email, age = 18, role = 'user' }) {
return {
name,
email,
age,
role,
id: generateId()
};
}
// Использование
const user = createUser({
name: "Анна",
email: "anna@example.com",
age: 25
});
// Rest: собираем аргументы в массив
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Spread: разворачиваем массив в аргументы
function multiply(a, b, c) {
return a * b * c;
}
const numbers = [2, 3, 4];
console.log(multiply(...numbers)); // 24
function fetchData(url, options = {}) {
const config = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
...options // Переопределяем значения по умолчанию
};
return fetch(url, config);
}
// Чистая функция легко тестируется
function calculateTax(amount, rate) {
return amount * rate;
}
// Простые тесты
console.assert(calculateTax(100, 0.1) === 10);
console.assert(calculateTax(0, 0.1) === 0);
console.assert(calculateTax(100, 0) === 0);
// Функция с побочными эффектами сложнее тестируется
function saveAndNotify(data) {
database.save(data); // Нужно мокать базу данных
emailService.send(data.email); // Нужно мокать email сервис
logger.log('Data saved'); // Нужно мокать логгер
}
// Функция для тестирования
function validatePassword(password) {
if (password.length < 8) {
return { valid: false, error: 'Пароль слишком короткий' };
}
if (!/[A-Z]/.test(password)) {
return { valid: false, error: 'Нужна заглавная буква' };
}
if (!/[0-9]/.test(password)) {
return { valid: false, error: 'Нужна цифра' };
}
return { valid: true };
}
// Тесты
describe('validatePassword', () => {
test('отклоняет короткие пароли', () => {
const result = validatePassword('123');
expect(result.valid).toBe(false);
expect(result.error).toBe('Пароль слишком короткий');
});
test('принимает валидные пароли', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(true);
});
});
// ❌ Плохо: создание функции в цикле
for (let i = 0; i < 1000; i++) {
setTimeout(function() {
console.log(i); // Создается 1000 функций
}, i * 100);
}
// ✅ Хорошо: переиспользование функции
function logNumber(number) {
console.log(number);
}
for (let i = 0; i < 1000; i++) {
setTimeout(() => logNumber(i), i * 100);
}
// ✅ Мемоизация для дорогих вычислений
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveCalculation = memoize(function(n) {
console.log('Вычисляем...');
return n * n * n;
});
console.log(expensiveCalculation(5)); // Вычисляем... 125
console.log(expensiveCalculation(5)); // 125 (из кэша)
Помните: функции — это не просто способ группировки кода, это мощный инструмент для создания качественных, поддерживаемых приложений. Изучение принципов работы с функциями — основа профессионального программирования на JavaScript.
Хочешь больше статей по подготовке к собеседованию? Подпишись на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляй сайт в избранное и прокачивай себя каждый день 💪