Получаем только изменённые поля объекта!
При изменении состояния приложения можно сравнить предыдущую и текущую версию объекта, чтобы определить только обновлённые значения. Такой diff позволяет отделить изменившиеся поля от тех, которые остались без изменений, и использовать только актуальные данные для дальнейшей обработки.
const oldUser = {
name: "Alex",
age: 30,
role: "developer"
};
const newUser = {
name: "Alex",
age: 31,
role: "developer"
};
Для получения изменённых полей можно преобразовать объект в массив пар через Object.entries(), отфильтровать только отличающиеся значения и собрать новый объект обратно с помощью Object.fromEntries().
const changes = Object.fromEntries(
Object.entries(newUser)
.filter(([key, value]) =>
!Object.is(value, oldUser[key])
)
);
console.log(changes);
В результате получится объект, содержащий только изменённые поля:
{
age: 31
}
Такой подход применяется при обновлении состояния во frontend-приложениях, работе с формами и подготовке частичных изменений для API через PATCH запросы.
Вместо отправки всей структуры можно передать только те значения, которые действительно были изменены.
const hasChanges =
Object.keys(changes).length > 0;
console.log(hasChanges);
Проверка через количество ключей позволяет определить, были ли изменения вообще. Если объект changes пустой, значит исходное и новое состояние полностью совпадают.
Важно учитывать, что данный способ выполняет только поверхностное сравнение (shallow diff) и проверяет значения первого уровня объекта.
const first = {
profile: {
name: "Alex"
}
};
const second = {
profile: {
name: "Alex"
}
};
console.log(first.profile === second.profile);
// false
Вложенные объекты сравниваются по ссылке, поэтому одинаковая структура внутри разных объектов не делает их равными. Для таких случаев требуется использовать глубокое сравнение (deep comparison) или отдельную логику построения diff.
🔥 Также при реализации реальных обновлений чере
📂 Напоминалка для работы с async/await!
Например, await приостанавливает выполнение текущей async-функции до завершения Promise, а try...catch позволяет удобно обрабатывать возникающие ошибки.
На картинке — основной синтаксис async/await, принцип его работы, сравнение с цепочками .then() и другие полезные моменты.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс
Склеивайте множество синхронных обновлений в одно!
Иногда функция может вызываться десятки раз за один проход синхронного кода.
Можно накопить изменения и запланировать обработку один раз:
const pending = new Set();
function update(id) {
pending.add(id);
scheduleFlush();
}
queueMicrotask() выполнит flush после текущего синхронного кода, но до перехода к следующей macrotask:
function flush() {
scheduled = false;
process([...pending]);
pending.clear();
}
Теперь даже серия вызовов обработается одной пачкой:
update(1);
update(2);
update(3);
// process([1, 2, 3]) — один раз
🔥 Такой подход объединяет множество быстрых обновлений в одну обработку, уменьшая лишние вычисления и нагрузку. Что крайне полезно для событий и часто обновляемых данных.
📣 JS Ready | #совет
📂 Напоминалка по деструктуризации!
Например, const [a, b] = arr позволяет получить значения из массива, а const { name, age } = user — извлечь нужные свойства из объекта.
На картинке — основные способы деструктуризации массивов и объектов, которые пригодятся в повседневной разработке.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс
Почему Array.sort() может создать баг далеко от места сортировки!
С sort() проблема не в самой сортировке, а в скрытой мутации. Вызов выглядит как обычное получение отсортированного результата, хотя одновременно изменяет сам исходный массив:
const scores = [30, 10, 20];
const sorted = scores.sort(
(a, b) => a - b
);
console.log(scores);
// [10, 20, 30]
sort() сортирует массив непосредственно и возвращает ту же ссылку. Поэтому изменение увидит не только код, работающий с sorted, но и любой код, который уже хранит ссылку на исходный массив:
const scores = [30, 10, 20];
const cached = scores;
scores.sort((a, b) => a - b);
console.log(cached);
// [10, 20, 30]
Особенно неприятно это становится на границе функций. По названию getSortedUsers() можно ожидать получение нового результата, но реализация незаметно меняет массив вызывающего кода:
function getSortedUsers(users) {
return users.sort(
(a, b) => a.age - b.age
);
}
В результате функция имеет побочный эффект, который не очевиден из места её вызова. Это уже может затронуть состояние, кэш, мемоизацию или другой код, использующий тот же массив:
const users = getUsers();
const original = users;
getSortedUsers(users);
console.log(original === users);
// true
Классический способ избежать этого — сначала создать новый массив и сортировать уже его. Такой код работает, но копирование приходится выражать отдельной операцией:
const sorted = [...scores].sort(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
toSorted() делает намерение явным: получить новый массив с другим порядком элементов, не изменяя исходный. При этом возвращается уже другая ссылка:
const sorted = scores.toSorted(
(a, b) => a - b
);
console.log(scores);
// [30, 10, 20]
console.log(scores === sorted);
// false
Важно, что копируется именно массив. Объекты внутри него не клонируются, поэтому обе коллекции по-прежнему содержат ссылки на одни и те же объекты:
const users = [
{ name: "Alex", age: 30 },
{ name: "Bob", age: 20 }
📂 Шпаргалка по JavaScript filter()!
Метод filter() используется для отбора элементов массива по заданному условию. Результатом выполнения является новый массив, при этом исходный массив не изменяется.
На изображении — синтаксис метода, аргументы callback-функции и примеры фильтрации чисел и объектов.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс
Почему JavaScript может изменить большой ID из API
Числовой идентификатор может быть корректно сформирован на сервере, но изменить значение после преобразования в js Number.
Причина заключается в модели представления чисел: Number соответствует IEEE 754 binary64 и не способен точно представить все целые числа произвольного размера:
const id = 9007199254740993;
console.log(id);
// 9007199254740992
Граница гарантированно точного представления целых чисел определяется константой Number.MAX_SAFE_INTEGER:
console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991
console.log(
Number.isSafeInteger(9007199254740991)
);
// true
Безопасный диапазон составляет от -(2^53 - 1) до 2^53 - 1. За его пределами проблема заключается не просто в том, что число становится «слишком большим»: соседние математические целые уже не обязаны иметь разные представления в Number. Поэтому два разных исходных значения могут оказаться одним и тем же значением javascript:
const a = 9007199254740992;
const b = 9007199254740993;
console.log(a === b);
// true
Для точных операций с большими целыми в js существует BigInt. Суффикс n создаёт значение непосредственно как BigInt, без промежуточного преобразования в Number:
const id = 9007199254740993n;
console.log(id + 1n);
// 9007199254740994n
С API возникает дополнительная граница — десериализация JSON. Сам JSON может содержать последовательность цифр 9007199254740993 как число, но стандартный JSON.parse() преобразует JSON-число в JavaScript Number.
Если значение находится за пределами безопасного целочисленного диапазона, точность теряется уже во время этого преобразования:
const data = JSON.parse(
'{"id":9007199254740993}'
);
console.log(data.id);
// 9007199254740992
После такого парсинга восстановить исходный ID по полученному Number в общем случае невозможно: информация о первоначальном целом уже потеряна.
Поэтому идентификаторы, над которыми не предполагается арифметика, надёжнее передавать в JSON как строки. Это сохраняет ис
📱 Web Workers — выполнение кода в отдельном потоке!
Разбираем Web Workers, с помощью которых можно выполнять ресурсоёмкие задачи вне основного UI-потока: запускать фоновые вычисления, передавать данные между main thread и Worker, получать результаты, обрабатывать ошибки и управлять завершением Worker. Используется для тяжёлых вычислений и обработки больших объёмов данных без блокировки интерфейса.
📣 JS Ready | #шпора
Выполняйте независимые операции без эффекта домино!
Если несколько операций не зависят друг от друга, Promise.all() может создать лишнюю связанность: одна ошибка отклоняет общий Promise, хотя остальные задачи продолжат выполняться и могут успешно завершиться.
const users = await Promise.all(
ids.map(id => loadUser(id))
);
Для независимых задач используйте Promise.allSettled() — он дождётся каждой и сохранит результат отдельно:
const results = await Promise.allSettled(
ids.map(id => loadUser(id))
);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
Что полезно для пакетной отправки, фоновых задач и загрузки независимых данных: ошибка одной операции не скрывает успешные результаты остальных.
🔥 Если операции независимы, не позволяйте одной ошибке скрывать результат остальных — обрабатывайте каждую отдельно.
📣 JS Ready | #совет
❤️ Немного ностальгии и чистого JS в статье на Хабре: «TAMA-90: тамагочи из 90-х своими руками на JavaScript»!
В этой статье:
• Показывается, как с нуля собрать браузерного тамагочи на HTML, CSS и JavaScript — без фреймворков, npm и сборщиков;
• Разбираются игровое состояние, жизненный цикл питомца, случайные события, мини-игра, коллекция и сохранение прогресса через localStorage;
• Объясняется, как рисовать пиксельных персонажей программно через Canvas и создавать звуки через Web Audio API без готовых изображений и аудиофайлов.
🔊 Продолжайте читать на Habr!
📣 JS Ready | #статья
📂 Шпаргалка по технологиям взаимодействия систем!
Например, REST используют для построения API, WebSocket — для двустороннего обмена данными, а gRPC — для взаимодействия между сервисами.
На картинке — основные технологии и подходы, с которыми регулярно сталкиваются разработчики: REST, Webhooks, GraphQL, SOAP, WebSocket, gRPC, MQTT, AMQP, SSE, EDI и EDA. Кратко и наглядно показано, чем они отличаются, как работают и для каких задач применяются.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс
📂 Как работает HTTP и HTTPS!
HTTP передаёт данные между клиентом и сервером без шифрования, а HTTPS добавляет TLS — он аутентифицирует сервер, согласовывает ключи и защищает дальнейший обмен данными шифрованием.
На картинке наглядно показано, чем отличается обычное HTTP-соединение от HTTPS: от TCP-handshake до установки защищённого соединения и передачи данных.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс
Используйте возвращаемое значение delete() вместо лишней проверки!
У Map.delete() и Set.delete() есть полезное свойство: метод возвращает true, только если элемент действительно существовал и был удалён.
Поэтому вместо двойного поиска:
if (cache.has(key)) {
cache.delete(key);
onRemoved(key);
}
можно сразу использовать результат удаления:
if (cache.delete(key)) {
onRemoved(key);
}
То же самое работает с Set:
if (activeUsers.delete(userId)) {
updateCounter();
}
Вы не выполняете сначала has(), а затем отдельный delete(). Сама операция удаления одновременно отвечает на вопрос, был ли элемент.
🔥 Не проверяйте коллекцию перед удалением, если проверка нужна только ради условия — delete() уже возвращает нужный результат.
📣 JS Ready | #совет
Подписывайтесь на событие только один раз через once!
У addEventListener() есть опция once: обработчик автоматически удаляется после первого срабатывания. Без ручного removeEventListener():
button.addEventListener('click', handleClick, {
once: true
});
Особенно полезно для одноразовых действий: завершения анимации, первого взаимодействия, загрузки ресурса или ожидания ответа.
element.addEventListener('transitionend', () => {
element.remove();
}, { once: true });
Можно удобно превращать одноразовые события в Promise:
const once = (target, event) =>
new Promise(resolve =>
target.addEventListener(event, resolve, { once: true })
);
await once(video, 'ended');
console.log('Видео закончилось');
🔥 Для одноразового события используйте { once: true }. Если ждёте одно из нескольких — снимайте оставшиеся обработчики через AbortController.
📣 JS Ready | #совет
Как оплачивать зарубежные сервисы в 2026 году?
Можно бегать между посредниками и бояться блокировок после оплаты, а можно выпустить международную карту Lumio Pay и пользоваться любимыми сервисами без рисков.
— выпуск карты за 2 минуты
— лучший курс пополнения на рынке (у конкурентов на 20% выше)
— пополнение рублями или криптой
— чистые BIN карт, оплата без риска блокировок
Пока все ищут идеальное решение, оно у тебя перед глазами: @LumioPay
Почему forEach() не ждёт async-функцию?
forEach() не учитывает возвращаемое значение callback и не содержит механизма ожидания Promise. Поэтому передача async-callback не меняет модель выполнения метода: forEach() синхронно запускает callback для каждого элемента, не ожидая завершения созданных им асинхронных операций:
const users = [1, 2, 3];
users.forEach(async (id) => {
await saveUser(id);
});
console.log("Готово");
"Готово" будет выведено до завершения saveUser(). Каждый вызов async-callback возвращает Promise, но forEach() эти Promise не ожидает и не собирает.
Если операции должны выполняться последовательно, ожидание необходимо сделать частью самого цикла. Для этого подходит for...of с await:
for (const id of users) {
await saveUser(id);
}
console.log("Готово");
Следующая итерация начнётся только после завершения предыдущей. Такой вариант нужен, когда важен порядок выполнения или операции зависят друг от друга.
Для независимых операций можно сформировать массив Promise и дождаться их через Promise.all():
await Promise.all(
users.map((id) => saveUser(id))
);
console.log("Готово");
Promise.all() успешно выполняется после выполнения всех переданных Promise и отклоняется, если хотя бы один из них завершается с ошибкой. При этом остальные уже запущенные операции автоматически не отменяются.
Есть и важное следствие для обработки ошибок: внешний try...catch вокруг forEach() не перехватит последующее отклонение Promise из async-callback:
try {
users.forEach(async (id) => {
await saveUser(id);
});
} catch (error) {
console.error(error);
}
Для управляемой обработки ошибки необходимо ожидать Promise, представляющий выполнение асинхронных операций:
try {
await Promise.all(
users.map((id) => saveUser(id))
);
} catch (error) {
console.error(error);
}
🔥 Проблема не в async внутри forEach(), а в контракте самого метода: forEach() игнорирует возвращаемое значение callback и не ожидает Promise. Для последовательного выполнения используйте
📂 Шпаргалка по разработке API!
Например, REST используется для построения HTTP API, а OAuth 2.0 и JWT помогают организовать авторизацию и безопасную передачу данных.
На картинке — основные темы API-разработки: методы и коды ответов, типы API, безопасность, документация, тестирование, интеграция и API Gateway.
Сохрани, чтобы не потерять!
📣 JS Ready | #ресурс