Операторы инкремента ++
и декремента --
имеют две формы:
i++
, i--
) — возвращает старое значение, затем изменяет переменную++i
, --i
) — сначала изменяет переменную, затем возвращает новое значениеlet a = 5;
console.log(a++); // 5 (возвращает старое значение)
console.log(a); // 6 (переменная изменилась)
let b = 5;
console.log(++b); // 6 (возвращает новое значение)
console.log(b); // 6 (переменная уже изменена)
Ключевое отличие: момент возврата значения относительно изменения переменной.
Алгоритм работы:
let counter = 10;
// Постфиксный инкремент
let oldValue = counter++;
console.log(oldValue); // 10 (старое значение)
console.log(counter); // 11 (новое значение)
// Постфиксный декремент
let score = 100;
let previousScore = score--;
console.log(previousScore); // 100 (старое значение)
console.log(score); // 99 (новое значение)
Алгоритм работы:
let counter = 10;
// Префиксный инкремент
let newValue = ++counter;
console.log(newValue); // 11 (новое значение)
console.log(counter); // 11 (то же значение)
// Префиксный декремент
let score = 100;
let updatedScore = --score;
console.log(updatedScore); // 99 (новое значение)
console.log(score); // 99 (то же значение)
Оператор | Название | Когда изменяется | Что возвращает | Пример |
---|---|---|---|---|
i++ | Постфиксный инкремент | После возврата | Старое значение | 5++ → возвращает 5 , i становится 6 |
++i | Префиксный инкремент | До возврата | Новое значение | ++5 → i становится 6 , возвращает 6 |
i-- | Постфиксный декремент | После возврата | Старое значение | 5-- → возвращает 5 , i становится 4 |
--i | Префиксный декремент | До возврата | Новое значение | --5 → i становится 4 , возвращает 4 |
// Постфиксная форма (классический цикл)
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
// Префиксная форма
for (let i = 0; i < 5; ++i) {
console.log(i); // 0, 1, 2, 3, 4
}
// В циклах результат одинаковый,
// так как возвращаемое значение не используется
let a = 5;
let b = 5;
// Постфиксная форма
let result1 = a++ * 2;
console.log(result1); // 10 (5 * 2)
console.log(a); // 6
// Префиксная форма
let result2 = ++b * 2;
console.log(result2); // 12 (6 * 2)
console.log(b); // 6
let attempts = 3;
// Постфиксная форма
if (attempts-- > 0) {
console.log(`Попытка ${attempts + 1}`); // "Попытка 3"
console.log(`Осталось попыток: ${attempts}`); // "Осталось попыток: 2"
}
let retries = 3;
// Префиксная форма
if (--retries > 0) {
console.log(`Попытка ${3 - retries}`); // "Попытка 1"
console.log(`Осталось попыток: ${retries}`); // "Осталось попыток: 2"
}
let arr = [10, 20, 30, 40, 50];
let index = 2;
// Постфиксная форма
console.log(arr[index++]); // 30 (arr[2])
console.log(index); // 3
index = 2; // сброс
// Префиксная форма
console.log(arr[++index]); // 40 (arr[3])
console.log(index); // 3
let x = 5;
let y = 5;
// Сложное выражение с постфиксом
let result1 = x++ + x++ + x++;
// x++ возвращает 5, x становится 6
// x++ возвращает 6, x становится 7
// x++ возвращает 7, x становится 8
// result1 = 5 + 6 + 7 = 18
console.log(result1); // 18
console.log(x); // 8
// Сложное выражение с префиксом
let result2 = ++y + ++y + ++y;
// ++y: y становится 6, возвращает 6
// ++y: y становится 7, возвращает 7
// ++y: y становится 8, возвращает 8
// result2 = 6 + 7 + 8 = 21
console.log(result2); // 21
console.log(y); // 8
let a = 10;
let b = 10;
// Смешивание префикса и постфикса
let mixed = a++ + ++a - --b + b--;
// a++ возвращает 10, a становится 11
// ++a: a становится 12, возвращает 12
// --b: b становится 9, возвращает 9
// b-- возвращает 9, b становится 8
// mixed = 10 + 12 - 9 + 9 = 22
console.log(mixed); // 22
console.log(a); // 12
console.log(b); // 8
let i = 5;
let j = i++ + ++i;
console.log(j);
console.log(i);
Результат:
Объяснение:
i++
возвращает 5
, затем i
становится 6
++i
увеличивает i
до 7
и возвращает 7
j = 5 + 7 = 12
let count = 0;
for (let i = 0; i < 3; i++) {
count += i++;
}
console.log(count);
Результат: count = 3
Объяснение:
i = 0
, count += 0
(i++ возвращает 0), i
становится 1
, затем i++
в цикле делает i = 2
i = 2
, count += 2
(i++ возвращает 2), i
становится 3
, цикл завершаетсяcount = 0 + 2 = 2
Внимание: Двойной инкремент в цикле — плохая практика!
let arr = ['a', 'b', 'c', 'd'];
let index = 1;
let result = arr[index++] + arr[++index];
console.log(result);
console.log(index);
Результат:
Объяснение:
arr[index++]
→ arr[1]
= "b"
, затем index
становится 2
arr[++index]
→ index
становится 3
, затем arr[3]
= "d"
result = "b" + "d" = "bd"
let x = 5;
let y = 5;
if (x++ > 5 || ++y > 5) {
console.log("Условие выполнено");
} else {
console.log("Условие не выполнено");
}
console.log(x, y);
Результат:
Объяснение:
a++
возвращает 3
, a
становится 4
++a
увеличивает a
до 5
, возвращает 5
3 * 5 = 15
a--
возвращает 5
, a
становится 4
--a
уменьшает a
до 3
, возвращает 3
5 * 3 = 15
result = 15 + 15 = 30
Исправление: Пересчитаем:
a++
возвращает 3
, a
= 4
++a
: a
= 5
, возвращает 5
a--
возвращает 5
, a
= 4
--a
: a
= 3
, возвращает 3
result = (3 * 5) + (5 * 3) = 15 + 15 = 30
// Классический счетчик
let pageViews = 0;
function trackPageView() {
console.log(`Просмотр страницы #${++pageViews}`);
// Префикс удобен, когда нужно новое значение
}
// Обработка элементов по порядку
let items = ['item1', 'item2', 'item3'];
let currentIndex = 0;
function getNextItem() {
if (currentIndex < items.length) {
return items[currentIndex++]; // Постфикс для получения текущего элемента
}
return null;
}
class IdGenerator {
constructor() {
this.currentId = 0;
}
// Префиксная форма для генерации нового ID
generateId() {
return `id_${++this.currentId}`;
}
// Постфиксная форма для получения текущего ID и переход к следующему
getCurrentAndNext() {
return {
current: `id_${this.currentId++}`,
next: `id_${this.currentId}`
};
}
}
const generator = new IdGenerator();
console.log(generator.generateId()); // "id_1"
console.log(generator.generateId()); // "id_2"
// Реализация стека
class Stack {
constructor() {
this.items = [];
this.top = -1;
}
push(item) {
this.items[++this.top] = item; // Префикс: сначала увеличиваем, потом используем
}
pop() {
if (this.top >= 0) {
return this.items[this.top--]; // Постфикс: сначала используем, потом уменьшаем
}
return undefined;
}
peek() {
return this.items[this.top];
}
}
// Поиск в массиве
function findElement(arr, target) {
let left = 0;
let right = arr.length - 1;
let comparisons = 0;
while (left <= right) {
console.log(`Сравнение #${++comparisons}`); // Префикс для подсчета
let mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return { index: mid, comparisons };
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return { index: -1, comparisons };
}
// Дебаунсинг с счетчиком
function createDebouncedFunction(func, delay) {
let timeoutId;
let callCount = 0;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
console.log(`Вызов #${++callCount}`); // Префикс для нового номера
func.apply(this, args);
}, delay);
};
}
// Ограничение количества попыток
function createRetryFunction(func, maxAttempts) {
let attempts = 0;
return async function(...args) {
while (attempts < maxAttempts) {
try {
console.log(`Попытка ${++attempts}`); // Префикс для текущей попытки
return await func.apply(this, args);
} catch (error) {
if (attempts >= maxAttempts) {
throw new Error(`Превышено максимальное количество попыток (${maxAttempts})`);
}
console.log(`Попытка ${attempts} неудачна, повторяем...`);
}
}
};
}
В JavaScript нет разницы в производительности между ++i
и i++
для примитивных типов, так как:
// Бенчмарк (результаты будут примерно одинаковые)
function benchmarkPrefix() {
let sum = 0;
for (let i = 0; i < 1000000; ++i) {
sum += i;
}
return sum;
}
function benchmarkPostfix() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
// Время выполнения будет практически одинаковым
console.time('prefix');
benchmarkPrefix();
console.timeEnd('prefix');
console.time('postfix');
benchmarkPostfix();
console.timeEnd('postfix');
// ✅ Используйте префикс, когда нужно новое значение
let counter = 0;
function getNextId() {
return `user_${++counter}`; // Сразу получаем новое значение
}
// ✅ Используйте постфикс, когда нужно текущее значение
let index = 0;
let items = ['a', 'b', 'c'];
function getNextItem() {
return items[index++]; // Получаем текущий элемент, затем переходим к следующему
}
// ✅ В циклах — без разницы, выбирайте по стилю команды
for (let i = 0; i < 10; i++) { /* постфикс — классика */ }
for (let i = 0; i < 10; ++i) { /* префикс — C++ стиль */ }
❌ Ошибка: Ожидание одинакового поведения
let a = 5;
let b = 5;
// Ошибочное ожидание одинакового результата
let result1 = a++ * 2; // 10 (5 * 2)
let result2 = ++b * 2; // 12 (6 * 2)
// result1 !== result2!
✅ Правильно: Понимать разницу в моменте возврата значения
// ❌ Плохо: двойной инкремент
for (let i = 0; i < 10; i++) {
console.log(i++);
// i увеличивается дважды за итерацию!
}
// ✅ Хорошо: один инкремент
for (let i = 0; i < 10; i++) {
console.log(i);
}
// ✅ Или альтернативно:
for (let i = 0; i < 10; ++i) {
console.log(i);
}
// ❌ Плохо: сложно читать и понимать
let x = 5;
let result = x++ + ++x * x-- - --x;
// ✅ Хорошо: разбить на шаги
let y = 5;
let step1 = y++; // 5, y = 6
let step2 = ++y; // 7, y = 7
let step3 = y--; // 7, y = 6
let step4 = --y; // 5, y = 5
let clearResult = step1 + step2 * step3 - step4;
// ❌ Undefined behavior: не делайте так!
let i = 5;
let bad = i++ + i++ + ++i; // Порядок вычисления не гарантирован
// ✅ Хорошо: одна модификация за выражение
let j = 5;
let good1 = j++; // 5
let good2 = j++; // 6
let good3 = ++j; // 8
let goodResult = good1 + good2 + good3; // 19
// ⚠️ Осторожно: может быть неочевидно
let attempts = 3;
while (attempts-- > 0) {
console.log(`Попытка ${attempts + 1}`); // Нужно +1 из-за постфикса
}
// ✅ Более понятно:
let retries = 3;
while (retries > 0) {
console.log(`Попытка ${retries}`);
retries--;
}
// Вместо ручного инкремента в циклах
const numbers = [1, 2, 3, 4, 5];
// ❌ Старый стиль
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i] * 2);
}
// ✅ Современный стиль
numbers.forEach(num => console.log(num * 2));
numbers.map(num => num * 2);
numbers.filter(num => num > 2);
// Вместо ручного управления индексами
const items = ['a', 'b', 'c', 'd'];
// ❌ С инкрементом
let index = 0;
const first = items[index++];
const second = items[index++];
// ✅ С деструктуризацией
const [firstItem, secondItem, ...rest] = items;
// ✅ Современный подход к счетчикам
function* createCounter(start = 0) {
let count = start;
while (true) {
yield ++count;
}
}
const counter = createCounter();
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
// Или с классом
class Counter {
constructor(start = 0) {
this.value = start;
}
next() {
return ++this.value;
}
current() {
return this.value;
}
}
// ✅ Хорошо: понятно что происходит
let index = 0;
while (index < items.length) {
processItem(items[index]);
index++;
}
// ⚠️ Менее понятно
let i = 0;
while (i < items.length) {
processItem(items[i++]);
}
// ✅ Постфикс: когда нужно текущее значение
function popFromStack(stack) {
return stack[--stack.length]; // Префикс: сначала уменьшаем размер
}
function pushToStack(stack, item) {
stack[stack.length++] = item; // Постфикс: используем текущую длину
}
// ✅ Префикс: когда нужно новое значение
function generateUniqueId() {
return `id_${++globalCounter}`;
}
// ❌ Плохо
let result = arr[i++] + arr[++i] * arr[i--];
// ✅ Хорошо
let current = arr[i];
i++;
let next = arr[i];
i++;
let afterNext = arr[i];
i--;
let result = current + next * afterNext;
// ✅ С комментариями
function processQueue(queue) {
while (queue.length > 0) {
// Берем первый элемент и сразу удаляем его из очереди
const item = queue[queue.length-- - 1];
processItem(item);
}
}
Ключевые моменты об операторах инкремента и декремента:
i++
) — возвращает старое значение, затем изменяет++i
) — изменяет сначала, затем возвращает новое значениеРекомендации:
Помните: Правильный выбор между i++
и ++i
зависит от контекста использования и того, какое значение вам нужно получить!
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и прокачивайтесь каждый день 💪