CreateCounter: примеры (JAVASCRIPT)

CreateCounter в JS: как создать счётчик с помощью замыканий
Раздел: Примеры кода, Замыкания
createCounter(initialValue (number)): function

Базовая информация о createCounter

Функция createCounter не является встроенной в JavaScript. Это популярный шаблон проектирования (паттерн), который используется для создания функций-счётчиков. Его основная цель — инкапсулировать состояние счётчика, защищая его от прямого внешнего изменения, и предоставлять контролируемый интерфейс для работы со значением.

Шаблон часто применяется, когда требуется:

  • Создать независимый счётчик с приватным состоянием.
  • Генерировать уникальные идентификаторы или последовательные номера.
  • Реализовать функционал с состоянием без использования классов (особенно до ES6).
  • Демонстрировать работу замыканий.

Реализация функции обычно принимает один необязательный аргумент:

  • startValue (Number): Начальное значение счётчика. По умолчанию часто равно 0.

Функция createCounter возвращает новую функцию (например, increment). Каждый вызов этой возвращаемой функции увеличивает внутреннее значение на единицу и возвращает новое значение. Более сложные реализации могут возвращать объект с несколькими методами (increment, decrement, reset, getValue).

Простые примеры использования

Пример базовой реализации, возвращающей функцию для увеличения счётчика:

function createCounter(start = 0) {
    let count = start;
    return function() {
        count += 1;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // Первый вызов
1
console.log(counter()); // Второй вызов
console.log(counter()); // Третий вызов
2
3

Пример с заданием начального значения:

const counterFrom10 = createCounter(10);
console.log(counterFrom10());
console.log(counterFrom10());
11
12

Пример реализации, возвращающей объект с методами:

function createCounter(start = 0) {
    let value = start;
    return {
        increment() { return ++value; },
        decrement() { return --value; },
        reset() { value = start; return value; },
        getValue() { return value; }
    };
}

const myCounter = createCounter(5);
console.log(myCounter.increment());
console.log(myCounter.getValue());
console.log(myCounter.decrement());
console.log(myCounter.reset());
6
6
5
5

Альтернативные подходы в JavaScript

Для управления состоянием счётчика можно использовать и другие конструкции языка:

  • Классы (ES6): Позволяют создавать инкапсулированные счётчики с методами. Предпочтительны для более сложных объектов, требующих множества методов и наследования.
    class Counter {
        constructor(start = 0) { this.value = start; }
        increment() { return ++this.value; }
        // ... другие методы
    }
    const counter = new Counter();
  • Генераторы (function*): Могут создавать итераторы, которые yield'ят следующее значение счётчика. Удобны для интеграции с итеративными протоколами.
    function* counterGenerator(start = 0) {
        let count = start;
        while(true) yield ++count;
    }
    const gen = counterGenerator();
    console.log(gen.next().value); // 1
  • Замыкания с несколькими внутренними переменными: Если нужно хранить сложное состояние, можно вернуть объект с несколькими функциями, как показано в примерах выше. Это классический функциональный подход.

Реализации на других языках

Концепция счётчика с инкапсулированным состоянием существует во многих языках, но реализация отличается.

Python: Использует замыкания или классы. Нет аналога в стандартной библиотеке, но реализуется похоже.

def create_counter(start=0):
    count = start
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter = create_counter()
print(counter()) # 1
1

PHP: Также поддерживает замыкания с использованием ключевого слова use и ссылки &.

function createCounter($start = 0) {
    $count = $start;
    return function() use (&$count) {
        return ++$count;
    };
}

$counter = createCounter();
echo $counter(); // 1
1

C: В чистом C нет замыканий. Состояние обычно хранят в статической переменной внутри функции или передают явно через указатель на структуру.

#include 
int counter() {
    static int count = 0; // Сохраняется между вызовами
    return ++count;
}
int main() {
    printf("%d\n", counter()); // 1
    printf("%d\n", counter()); // 2
    return 0;
}
1
2

Распространённые ошибки

1. Ожидание независимости счётчиков при ошибочном создании: Создание счётчика в цикле без сохранения ссылки приводит к созданию множества независимых функций, но если ссылка не сохраняется, работать будет только последняя.

const counters = [];
for (let i = 0; i < 3; i++) {
    // Для каждого i создаётся НОВОЕ лексическое окружение - всё правильно.
    counters.push(() => {
        let count = 0; // Ошибка: count инициализируется заново при каждом вызове функции!
        return ++count;
    });
}
console.log(counters[0]()); // 1
console.log(counters[0]()); // 2
console.log(counters[1]()); // 1
// Это не ошибка, но часто путают с замыканием на переменную i.
1
2
1
Правильно было бы использовать createCounter.

2. Попытка прямого доступа к внутренней переменной: Внешний код не может изменить инкапсулированную переменную.

const counter = createCounter();
console.log(counter.count); // undefined
counter.count = 100; // Не влияет на внутренний count
console.log(counter()); // Счётчик продолжит с 1 (или со следующего числа)
undefined
1

История изменений

Поскольку createCounter является паттерном, а не встроенной функцией, её реализация эволюционировала с развитием JavaScript. С появлением ES6 (ECMAScript 2015) стали доступны более элегантные альтернативы:

  • Ключевые слова let и const для блочной видимости, что делает рассуждения о замыканиях проще.
  • Стрелочные функции, позволяющие писать более краткий код.
  • Классы для объектно-ориентированной реализации.
  • Генераторы для создания итераторов-счётчиков.

Стандарт ES2022 официально не внёс изменений в этот паттерн.

Расширенные примеры

1. Счётчик с возможностью изменения шага инкремента:

Пример javascript
function createSteppableCounter(start = 0, step = 1) {
    let value = start;
    return {
        next() { value += step; return value; },
        setStep(newStep) { step = newStep; },
        getValue() { return value; }
    };
}

const stepCounter = createSteppableCounter(0, 5);
console.log(stepCounter.next()); // 5
stepCounter.setStep(3);
console.log(stepCounter.next()); // 8
5
8

2. Счётчик, который возвращает строку с префиксом:

Пример javascript
function createPrefixedCounter(prefix, start = 1) {
    let id = start;
    return () => `${prefix}-${id++}`;
}

const getOrderId = createPrefixedCounter('ORDER', 1000);
console.log(getOrderId()); // ORDER-1000
console.log(getOrderId()); // ORDER-1001
ORDER-1000
ORDER-1001

3. Счётчик с историей изменений:

Пример javascript
function createCounterWithHistory(initial = 0) {
    let current = initial;
    const history = [initial];
    return {
        increment() {
            current++;
            history.push(current);
            return current;
        },
        getHistory() { return [...history]; }, // возвращаем копию
        reset() {
            current = initial;
            history.length = 0;
            history.push(initial);
        }
    };
}

const histCounter = createCounterWithHistory();
histCounter.increment();
histCounter.increment();
console.log(histCounter.getHistory()); // [0, 1, 2]
[0, 1, 2]

4. Создание множества независимых счётчиков с помощью фабрики:

Пример javascript
const idGenerator = (function() {
    let globalCounter = 0;
    return {
        createCounter(start = 0) {
            let localCount = start;
            return {
                id: ++globalCounter, // Уникальный ID для каждого счётчика
                next() { return ++localCount; }
            };
        },
        getGlobalCount() { return globalCounter; }
    };
})();

const c1 = idGenerator.createCounter();
const c2 = idGenerator.createCounter(10);
console.log(c1.id, c1.next()); // 1, 1
console.log(c2.id, c2.next()); // 2, 11
console.log(idGenerator.getGlobalCount()); // 2
1 1
2 11
2

JS createCounter function comments

En
CreateCounter Creates a closure-based counter function