Изучаем функции

2026-02-25
16 мин.

Введение

В языках C/C++ функция — это, по сути, указатель на блок кода в памяти. В JavaScript функция — это значение, объект первого класса (first-class citizen). Она может всё то же, что и переменная: храниться в массиве, передаваться как аргумент и, самое интересное, возвращаться из другой функции.

Если в C++ или Pascal функции — это «глаголы» (сделай то, сделай это), то в JavaScript функция может быть инструментом, который создаёт другие узкоспециализированные инструменты. Это делает код гибким, как конструктор LEGO. Однако для понимания таких возможностей нужно разобраться с контекстом выполнения, областью видимости и замыканиями.

1. Способы объявления функций

2. Параметры и аргументы

3. Область видимости и замыкания (Closures)

Глобальная и локальная области видимости

Переменные, объявленные внутри функции, недоступны снаружи.

Замыкание

Замыкание — это способность функции «запоминать» своё лексическое окружение (переменные родительской функции) даже после того, как родительская функция завершила работу.

Пример 1: Счётчик (инкапсуляция)

function createCounter() {
  let count = 0; // «скрытая» переменная
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

const otherCounter = createCounter();
console.log(otherCounter()); // 1

// Доступа к переменной count извне нет

Пример 2: Генератор скидок (бизнес-кейс)

function createDiscount(percent) {
  // Внутренняя функция «запоминает» значение percent
  return function(price) {
    const discountAmount = price * (percent / 100);
    return price - discountAmount;
  };
}

const applyBlackFriday = createDiscount(50);
const applySeasonal = createDiscount(20);
const applySilverStatus = createDiscount(10);

const laptopPrice = 100000;
console.log(applyBlackFriday(laptopPrice));  // 50000
console.log(applySeasonal(laptopPrice));     // 80000
console.log(applySilverStatus(laptopPrice)); // 90000

Вопрос: Где физически хранится percent после завершения createDiscount?
Ответ: В замыкании. Память не освобождается, пока существует ссылка на возвращённую функцию.

Вопрос: Можно ли изменить процент скидки у уже созданной функции?
Ответ: Нет, если не предусмотрен специальный метод. Это и есть инкапсуляция.

4. Функции высшего порядка

Функция высшего порядка — это функция, которая принимает другую функцию как аргумент и/или возвращает функцию.

4.1 Функция как аргумент (callback)

function sayHello() {
  console.log("Hello");
}

function run(fn) {
  fn(); // вызываем переданную функцию
}

run(sayHello); // передаётся сама функция, не результат её вызова

Важно: sayHello — это функция, sayHello() — результат её вызова.

4.2 Массив функций

const actions = [
  () => console.log("AA"),
  () => console.log("BB"),
  () => console.log("CC")
];

actions[1](); // BB

Зачем нужен массив функций?

Пример: множество фильтров для массива товаров.

let filters = [];
filters[0] = el => el.price > 1000;
filters[1] = el => el.rate > 3.5;
filters[2] = el => el.categories === 'шина';

products.filter(prod => filters.every(fn => fn(prod)));

4.3 Возврат функции из функции

Возврат функции позволяет создавать гибкий и безопасный код, который генерирует нужные инструменты «на лету». Рассмотрим несколько типичных ситуаций.

4.3.1 Валидаторы (защита от дурака)

function minLength(min) {
  return function(text) {
    return text.length >= min;
  };
}

const checkPassword = minLength(8);
const checkUsername = minLength(3);

console.log(checkPassword("123")); // false
console.log(checkUsername("jd"));  // false

4.3.2 Обработчики событий (UI)

function createDeleteHandler(itemId) {
  return function(event) {
    console.log(`Удаляем объект с ID: ${itemId}`);
    // здесь запрос к API
  };
}

// При создании кнопки:
// button.onclick = createDeleteHandler(item.id);

Почему нельзя просто deleteItem(id)?
Потому что браузерный обработчик передаёт в функцию только объект события. Замыкание позволяет «склеить» нужный id и событие.

4.3.3 Персонализация (шаблоны приветствий)

function createGreeter(phrase) {
  return function(name) {
    return `${phrase}, ${name}!`;
  };
}

const sayHi = createGreeter("Привет");
const sayHello = createGreeter("Здравствуйте");

console.log(sayHi("Иван"));   // "Привет, Иван!"
console.log(sayHello("Петр")); // "Здравствуйте, Петр!"

4.3.4 Настраиваемая сортировка

const users = [
  { name: 'Ivan', age: 25, group: 3 },
  { name: 'Anna', age: 30, group: 5 },
  { name: 'Boris', age: 20, group: 1 }
];

function sortBy(field) {
  return function(a, b) {
    if (a[field] > b[field]) return 1;
    if (a[field] < b[field]) return -1;
    return 0;
  };
}

users.sort(sortBy('name')); // сортировка по имени
users.sort(sortBy('age'));  // по возрасту
users.sort(sortBy('group')); // по группе

4.3.5 Форматтер валют

function createFormatter(symbol, position = 'after') {
  return function(amount) {
    const fixedAmount = amount.toFixed(2);
    return position === 'before'
      ? `${symbol}${fixedAmount}`
      : `${fixedAmount}${symbol}`;
  };
}

const toUSD = createFormatter('$', 'before');
const toEUR = createFormatter('€');

console.log(toUSD(100));   // "$100.00"
console.log(toEUR(45.5));  // "45.50€"

4.3.6 Частичное применение (Partial Application)

function createRequester(baseUrl) {
  return function(endpoint) {
    return function(params) {
      console.log(`Sending GET to ${baseUrl}${endpoint} with query ${params}`);
    };
  };
}

const myApi = createRequester('https://api.myapp.com');
const getUsers = myApi('/users'); // зафиксировали домен и эндпоинт

getUsers('?id=42'); // финальный вызов

5. Чистые функции и побочные эффекты

В веб-разработке важно минимизировать изменение глобальных состояний. Чистая функция:

6. Продвинутые паттерны (middle level)

6.1 Debounce (оптимизация производительности)

function debounce(callback, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => callback(...args), delay);
  };
}

const search = (query) => console.log("Запрос в БД:", query);
const optimizedSearch = debounce(search, 500);

6.2 Memoize (кэширование результатов)

function memoize(fn) {
  const cache = {};
  return function(n) {
    if (n in cache) {
      return cache[n];
    }
    let result = fn(n);
    cache[n] = result;
    return result;
  };
}

const slowSquare = (x) => x * x;
const fastSquare = memoize(slowSquare);

7. Мастер-уровень (master level)

7.1 Настраиваемая сортировка со стратегиями

const Strategies = {
  default: (a, b) => a - b,
  text: (a, b) => a.localeCompare(b, 'ru', { sensitivity: 'base' }),
  date: (a, b) => new Date(a) - new Date(b),
  priority: (order) => (a, b) => {
    const weights = order;
    return weights[a] - weights[b];
  }
};

function sortBy(field, strategy = Strategies.default) {
  return function(objA, objB) {
    return strategy(objA[field], objB[field]);
  };
}

const users = [
  { name: 'Ivan', role: 'guest', regDate: '2023-05-01' },
  { name: 'Anna', role: 'admin', regDate: '2022-12-15' },
  { name: 'Борис', role: 'editor', regDate: '2024-01-10' }
];

// Сортировка по имени с текстовой стратегией
const sortedByName = users.toSorted(sortBy('name', Strategies.text));
console.log(sortedByName);

// Сортировка по роли с приоритетами
const roleWeight = { admin: 3, editor: 2, guest: 1 };
users.sort(sortBy('role', Strategies.priority(roleWeight)));

// Добавим направление сортировки
function withDirection(comparator, reverse = false) {
  return function(a, b) {
    return reverse ? comparator(a, b) * -1 : comparator(a, b);
  };
}
users.sort(withDirection(sortBy('name'), true)); // обратный порядок

7.2 Композиция фильтров (Filter Factory)

const products = [
  { name: 'iPhone', price: 1000, category: 'electronics', stock: 5 },
  { name: 'Book', price: 20, category: 'books', stock: 100 },
  { name: 'Laptop', price: 1500, category: 'electronics', stock: 0 },
  { name: 'T-shirt', price: 25, category: 'clothes', stock: 10 }
];

const priceAbove = (min) => (item) => item.price >= min;
const isCategory = (cat) => (item) => item.category === cat;
const inStock = () => (item) => item.stock > 0;

function and(...filters) {
  return function(item) {
    return filters.every(filterFunc => filterFunc(item));
  };
}

const complexFilter = and(
  isCategory('electronics'),
  priceAbove(500),
  inStock()
);

const result = products.filter(complexFilter);
console.log(result); // [ { name: 'iPhone', ... } ]

Заключение

В C для подобного функционала пришлось бы создавать массивы указателей на функции и передавать их в другие функции — это громоздко. В JavaScript мы можем строить «конструкторы поведения»: функции, которые принимают стратегии или правила и возвращают готовые к использованию специализированные функции, используя замыкания для сохранения контекста. Такой подход делает код декларативным, гибким и легко расширяемым.

Ключевые идеи:

ММФ-ноутс, ммф нотс, ммф-ноутс, mmf, notes, mmf-notes