😢 Оператор delete в JavaScript замедляет ваш код!
Многие разработчики не знают, что
──
💁♂️ Главная проблема
Современные JavaScript-движки (например, V8 в Chrome и Node.js) используют внутреннюю оптимизацию, называемую Hidden Classes или Shapes.
Когда вы создаёте объект:
Движок создаёт внутреннюю форму — «объект с ключами a и b». Все похожие объекты будут использовать одну и ту же форму, а доступ к свойствам (
Теперь, если вы делаете:
Вы ломаете форму объекта. V8 вынужден переключить объект в так называемый dictionary mode — медленный режим работы как у обычного хэш-словаря.
👉 Результат: объект перестаёт быть оптимизированным, и все операции с ним становятся заметно медленнее.
👉 Итог: никогда не используйте delete внутри «горячего» кода (циклы, часто вызываемые функции). Это почти гарантированно приведёт к деградации производительности.
──
🤯 Delete не делает то, что вы думаете
Если вы ожидали, что
──
🫥 Delete портит массивы
Такие пустые элементы ломают работу
──
🤔 Что использовать вместо delete
▪️ Для объектов
▫️ Если нужно удалить свойство перед отправкой в API или JSON:
▫️ Если нужно просто «стереть» значение:
▫️ Если свойства часто добавляются и удаляются:
Используйте
▪️ Для массивов
▫️ Удалить по индексу:
▫️ Удалить по условию:
──
✅ Вывод
⚡️ Вместо него используйте:
—
—
—
#javascript #frontend
Многие разработчики не знают, что
delete — это настоящий «убийца производительности» в JavaScript. Он не сломан, но непредсказуем, медленный и часто делает не то, что вы думаете. 😅──
💁♂️ Главная проблема
Современные JavaScript-движки (например, V8 в Chrome и Node.js) используют внутреннюю оптимизацию, называемую Hidden Classes или Shapes.
Когда вы создаёте объект:
const obj = { a: 1, b: 2 };Движок создаёт внутреннюю форму — «объект с ключами a и b». Все похожие объекты будут использовать одну и ту же форму, а доступ к свойствам (
obj.a) будет молниеносным ⚡️Теперь, если вы делаете:
delete obj.b;
Вы ломаете форму объекта. V8 вынужден переключить объект в так называемый dictionary mode — медленный режим работы как у обычного хэш-словаря.
👉 Результат: объект перестаёт быть оптимизированным, и все операции с ним становятся заметно медленнее.
👉 Итог: никогда не используйте delete внутри «горячего» кода (циклы, часто вызываемые функции). Это почти гарантированно приведёт к деградации производительности.
──
🤯 Delete не делает то, что вы думаете
delete не удаляет переменные и не освобождает память напрямую, он удаляет только свойства объектов.var x = 1;
delete x; // false, x остаётся
console.log(x); // 1
let y = 2;
delete y; // SyntaxError (в строгом режиме)
const z = 3;
delete z; // SyntaxError
Если вы ожидали, что
delete «освободит память», то нет — это делает сборщик мусора, а не delete.──
🫥 Delete портит массивы
delete не удаляет элемент массива, он просто делает «дыру» (empty slot):let fruits = ['apple', 'banana', 'cherry'];
delete fruits[1];
console.log(fruits); // ['apple', <1 empty item>, 'cherry']
console.log(fruits.length); // 3 ❗️
Такие пустые элементы ломают работу
map, forEach и других итераторов — они просто пропускают «дырки».──
🤔 Что использовать вместо delete
▪️ Для объектов
▫️ Если нужно удалить свойство перед отправкой в API или JSON:
// ❌ Плохо
// delete user.password;
// send(user);
// ✅ Хорошо
const { password, ...safeUser } = user;
send(safeUser);
▫️ Если нужно просто «стереть» значение:
obj.password = null; // сохраняем форму объекта, работает быстро
▫️ Если свойства часто добавляются и удаляются:
Используйте
Map:let cache = new Map();
cache.set('token', 123);
cache.delete('token'); // Быстро и безопасно ✅
▪️ Для массивов
▫️ Удалить по индексу:
let fruits = ['apple', 'banana', 'cherry'];
fruits.splice(1, 1);
// ['apple', 'cherry']
▫️ Удалить по условию:
let numbers = [1, 2, 3, 4, 5];
numbers = numbers.filter(n => n !== 3);
// [1, 2, 4, 5]
──
✅ Вывод
delete — это низкоуровневый, неинтуитивный и медленный оператор, который вредит оптимизациям движка и создаёт трудноотлавливаемые баги.⚡️ Вместо него используйте:
—
obj.prop = null — быстро и безопасно—
Map для динамических коллекций—
splice и filter для массивов#javascript #frontend
❤2
😕 Как localStorage может сломать ваш сайт или приложение
Многие не знают, но обращение к
Ошибки могут возникать по двум основным причинам: при записи или при доступе к хранилищу.
──
📦 Ошибка при записи —
Самая распространённая проблема. Происходит, когда вы пытаетесь записать слишком много данных.
▪️ Что происходит:
▪️ Почему:
Браузер выделяет под данные сайта ограниченное место — обычно около 5–10 МБ. Если попытаться записать больше, то браузер заблокирует операцию.
▪️ Пример:
──
🛡 Ошибка при доступе —
Более коварная ошибка. Может возникнуть даже при чтении
▪️ Что происходит:
Даже попытка выполнить
▪️ Почему:
Браузер полностью заблокировал доступ к хранилищу из соображений приватности.
▪️ Основные причины:
— Пользователь отключил cookies и данные сайтов в настройках.
— Используется режим инкогнито (в некоторых браузерах он блокирует
— Скрипт выполняется внутри sandboxed iframe без
▪️ Пример:
──
🤔 Как защититься?
В одной из моих библиотек я реализовал класс
Такой подход гарантирует, что приложение не упадёт при любых ошибках доступа к storage.
👉 Посмотреть реализацию можно здесь:
https://github.com/webeach/react-hooks/blob/main/src/classes/SafeStorage/SafeStorage.ts
──
💡 Совет
Если вы используете
Также вы можете воспользоваться готовыми безопасными хуками из моей библиотеки
▪️ Документация хуков:
— useLocalStorage
— useSessionStorage
#javascript #frontend #react
Многие не знают, но обращение к
localStorage или sessionStorage может вызвать ошибку — и ваш сайт внезапно упадёт.Ошибки могут возникать по двум основным причинам: при записи или при доступе к хранилищу.
──
📦 Ошибка при записи —
QuotaExceededErrorСамая распространённая проблема. Происходит, когда вы пытаетесь записать слишком много данных.
▪️ Что происходит:
localStorage.setItem() падает с ошибкой QuotaExceededError (или DOMException с этим именем), если лимит хранилища превышен.▪️ Почему:
Браузер выделяет под данные сайта ограниченное место — обычно около 5–10 МБ. Если попытаться записать больше, то браузер заблокирует операцию.
▪️ Пример:
try {
localStorage.setItem('my_data', '...очень много данных...');
} catch (error) {
const isQuotaError =
error &&
(
error.name === 'QuotaExceededError' || // стандарт
error.name === 'NS_ERROR_DOM_QUOTA_REACHED' || // Firefox
error.code === 22 || // старый WebKit
error.code === 1014 // Firefox старый
);
if (isQuotaError) {
console.log('Переполнено localStorage');
} else {
console.log('Другая ошибка:', error);
}
}──
🛡 Ошибка при доступе —
SecurityErrorБолее коварная ошибка. Может возникнуть даже при чтении
window.localStorage.▪️ Что происходит:
Даже попытка выполнить
const storage = window.localStorage; может выбросить SecurityError.▪️ Почему:
Браузер полностью заблокировал доступ к хранилищу из соображений приватности.
▪️ Основные причины:
— Пользователь отключил cookies и данные сайтов в настройках.
— Используется режим инкогнито (в некоторых браузерах он блокирует
localStorage).— Скрипт выполняется внутри sandboxed iframe без
allow-storage.▪️ Пример:
let storage;
try {
storage = window.localStorage;
storage.setItem('__test__', '1');
storage.removeItem('__test__');
} catch (e) {
console.error('Ошибка: хранилище недоступно!', e.name);
// используем in-memory fallback
}
──
🤔 Как защититься?
В одной из моих библиотек я реализовал класс
SafeStorage, который оборачивает все операции с хранилищем в try/catch. Такой подход гарантирует, что приложение не упадёт при любых ошибках доступа к storage.
👉 Посмотреть реализацию можно здесь:
https://github.com/webeach/react-hooks/blob/main/src/classes/SafeStorage/SafeStorage.ts
──
💡 Совет
Если вы используете
localStorage или sessionStorage в React‑приложении, лучше вынести всю работу с ними в отдельный хук. Там можно централизованно обработать ошибки, создать fallback‑хранилище и гарантировать, что приложение не упадёт даже в самых неожиданных сценариях.Также вы можете воспользоваться готовыми безопасными хуками из моей библиотеки
@webeach/react-hooks:import { useLocalStorage } from '@webeach/react-hooks/useLocalStorage';
import { useSessionStorage } from '@webeach/react-hooks/useSessionStorage';
export function MyComponent() {
const [count, setCount] = useLocalStorage<number>('app:count', 0);
const [step, setStep] = useSessionStorage<number>('wizard:step', 1);
// ...
}▪️ Документация хуков:
— useLocalStorage
— useSessionStorage
#javascript #frontend #react
👍2
😌 Особенности работы JSON в JavaScript, о которых должен знать каждый!
Разберём всё по пунктам 👇
──
1️⃣ Удаление несовместимых свойств
При сериализации JSON сохраняет только:
▫️ объекты
▫️ массивы
▫️ строки
▫️ числа
▫️
▫️
Все остальные типы будут удалены.
──
2️⃣ Автозамена на
С объектами — значения удаляются.
Но в массивах JSON обязан сохранить "структуру", поэтому несовместимые элементы заменяются на
Это логично:
──
3️⃣
Хотя они являются числами, JSON их не поддерживает.
▫️ В объектах → удаляются
▫️ В массивах → превращаются в
──
4️⃣ Особенный случай —
Тут всё жёстче: JSON не умеет сериализовать
Его нужно убирать вручную или преобразовывать.
──
5️⃣ Первый уровень JSON может быть чем угодно
Не только объект или массив!
Можно сериализовать любое поддерживаемое значение.
──
6️⃣ Метод
Вы можете контролировать сериализацию любого объекта.
Механизм:
▫️
▫️ если на значении есть метод
▫️ возвращённое значение становится финальным
──
7️⃣ Секретный
Мало кто знает, но у
Он позволяет преобразовывать значения во время парсинга.
Например, превращаем дату из строки обратно в объект
──
8️⃣ Легендарный баг с
До ES2019 была странная несовместимость между спецификацией JSON (RFC 7159) и JavaScript-строками.
Символы U+2028 и U+2029 нельзя было использовать в JSON, но JS позволял их в строках.
Из-за этого:
▫️ сервер отправлял JSON с сырыми U+2028
▫️ браузер получал невалидный JSON
▫️
Как исправили:
С ES2019 JavaScript стал JSON Superset, теперь:
▫️ JavaScript-строки совместимы с JSON
▫️
▫️
──
⭐️ Сохраняй, чтобы не забыть и удивить своими знаниями на собеседовании любого интервьювера!
#javascript #frontend
JSON.stringify и JSON.parse выглядят простыми, но под капотом у них есть много важных особенностей и неожиданных нюансов, которые могут привести к багам.Разберём всё по пунктам 👇
──
1️⃣ Удаление несовместимых свойств
При сериализации JSON сохраняет только:
▫️ объекты
▫️ массивы
▫️ строки
▫️ числа
▫️
true/false▫️
nullВсе остальные типы будут удалены.
const data = {
one: 'value',
two: true,
three: false,
four: null,
five: 5,
six: [6],
seven: { prop: 7 },
eight: undefined, // ❌ будет удалено
nine: Symbol(), // ❌ будет удалено
ten: () => 10, // ❌ будет удалено
};
console.log(JSON.stringify(data));──
2️⃣ Автозамена на
null внутри массиваС объектами — значения удаляются.
Но в массивах JSON обязан сохранить "структуру", поэтому несовместимые элементы заменяются на
null.const data = [
'value',
true,
null,
[6],
{ prop: 7 },
undefined, // → null
Symbol(), // → null
() => 10, // → null
];
Это логично:
[1, undefined, 3] → [1, null, 3]
──
3️⃣
NaN и InfinityХотя они являются числами, JSON их не поддерживает.
▫️ В объектах → удаляются
▫️ В массивах → превращаются в
nullJSON.stringify([1, NaN, Infinity])
// → "[1,null,null]"
──
4️⃣ Особенный случай —
bigintТут всё жёстче: JSON не умеет сериализовать
bigint и выбрасывает ошибку.const data = { one: 10n };
JSON.stringify(data);
// ❌ TypeError: Do not know how to serialize a BigIntЕго нужно убирать вручную или преобразовывать.
──
5️⃣ Первый уровень JSON может быть чем угодно
Не только объект или массив!
Можно сериализовать любое поддерживаемое значение.
JSON.stringify(true) // 'true'
JSON.stringify(1) // '1'
JSON.stringify('hello') // '"hello"'
JSON.stringify(null) // 'null'
JSON.parse('true') // true
JSON.parse('"hi"') // 'hi'
JSON.parse('1') // 1
──
6️⃣ Метод
toJSONВы можете контролировать сериализацию любого объекта.
Механизм:
▫️
JSON.stringify рекурсивно обходит свойства▫️ если на значении есть метод
toJSON → вызывает его▫️ возвращённое значение становится финальным
const animal = {
name: 'Cat',
color: 'Black',
toJSON() {
return `${this.color} ${this.name}`;
},
};
console.log(JSON.stringify({ animal }));
// { "animal": "Black Cat" }──
7️⃣ Секретный
reviver в JSON.parseМало кто знает, но у
JSON.parse есть второй аргумент.Он позволяет преобразовывать значения во время парсинга.
Например, превращаем дату из строки обратно в объект
Date:const json = '{ "name": "Task 1", "due": "2025-12-01T15:00:00.000Z" }';
const task = JSON.parse(json, (key, value) => {
// (Это простой пример, в реальности проверка должна быть надежнее)
if (key === 'due') {
return new Date(value);
}
return value;
});
console.log(task.due.getFullYear()); // 2025──
8️⃣ Легендарный баг с
U+2028 и U+2029До ES2019 была странная несовместимость между спецификацией JSON (RFC 7159) и JavaScript-строками.
Символы U+2028 и U+2029 нельзя было использовать в JSON, но JS позволял их в строках.
Из-за этого:
▫️ сервер отправлял JSON с сырыми U+2028
▫️ браузер получал невалидный JSON
▫️
JSON.parse() бросал SyntaxErrorКак исправили:
С ES2019 JavaScript стал JSON Superset, теперь:
▫️ JavaScript-строки совместимы с JSON
▫️
JSON.stringify автоматически экранирует U+2028/U+2029▫️
JSON.parse их корректно принимает──
#javascript #frontend
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🧐 Какие способы хранения данных есть в браузере?
Эта тема — частый вопрос на собеседованиях.
Сейчас мы разберём все реальные способы хранения данных в браузере, разложим их по полочкам и сделаем так, чтобы на этот вопрос вы могли отвечать чётко, уверенно и красиво на любом интервью 👇
──
1️⃣ Cookie
Механизм хранения небольших данных, доступный и клиенту, и серверу.
🔸 Недостатки:
— Маленький лимит (≈ 4 KB на 1 cookie).
— Передаются на сервер при каждом запросе — лишний трафик.
— Ограниченная безопасность (если не использовать HttpOnly).
🔹 Что лучше всего хранить:
— Сессионные идентификаторы.
— Авторизационные токены (через HttpOnly + Secure).
— Минимальные настройки, нужные серверу.
Существует также современный Cookie Store API, который позволяет работать с cookie асинхронно и доступен даже внутри Service Worker.
──
2️⃣ Storage API (
Простое синхронное строковое хранилище.
🔸 Недостатки:
— Лимиты около 5–10 MB.
— Хранит только строки.
— Синхронное API может блокировать поток.
🔹 Что лучше всего хранить:
— Настройки UI.
— Лёгкие состояния.
— Временные данные текущей вкладки.
──
3️⃣ indexedDB
Асинхронная объектная база данных в браузере. Позволяет хранить большие и сложные структуры данных.
🔸 Недостатки:
— Сложное нативное API (callbacks, транзакции).
— Требует дополнительных библиотек для комфортной работы.
— Не лучший вариант для огромных бинарных файлов.
🔹 Что лучше всего хранить:
— Структурированные данные.
— Таблицы, списки, каталоги.
— Большие объёмы KV.
Почти все реальные проекты используют библиотеку Dexie.js, которая делает IndexedDB простой и удобной
А вот так выглядит ванильный пример (хуже для реальных проектов):
──
4️⃣ Origin Private File System (OPFS)
Приватная виртуальная файловая система для сайта.
🔸 Недостатки:
— Не подходит для мелких структур.
— Строгие квоты в Safari.
🔹 Что лучше всего хранить:
— Большие файлы: видео, изображения, аудио.
— Потоковые данные.
— ML-модели, WASM-данные.
──
5️⃣ Cache API
Управляемый HTTP‑кэш (работает и в Window, и в Service Worker).
🔸 Недостатки:
— Ключом является Request → не настоящий KV.
— Может очищаться браузером.
🔹 Что лучше всего хранить:
— Статические ресурсы (HTML, CSS, JS, шрифты).
— API‑ответы (GET), оффлайн‑кэш.
— Большие JSON и данные, выглядящие как «ресурсы».
⚠️ Здесь мы используем Cache API как KV — это рабочий, но хаковый способ. Лучше использовать IndexedDB или Storage API.
──
💥 Полезно знать
❗️ В пост не поместилось, смотрите продолжение в комментариях 👇
──
⭐ Сохраняй, чтобы не забыть!
#javascript #frontend
Эта тема — частый вопрос на собеседованиях.
Сейчас мы разберём все реальные способы хранения данных в браузере, разложим их по полочкам и сделаем так, чтобы на этот вопрос вы могли отвечать чётко, уверенно и красиво на любом интервью 👇
──
1️⃣ Cookie
Механизм хранения небольших данных, доступный и клиенту, и серверу.
🔸 Недостатки:
— Маленький лимит (≈ 4 KB на 1 cookie).
— Передаются на сервер при каждом запросе — лишний трафик.
— Ограниченная безопасность (если не использовать HttpOnly).
🔹 Что лучше всего хранить:
— Сессионные идентификаторы.
— Авторизационные токены (через HttpOnly + Secure).
— Минимальные настройки, нужные серверу.
document.cookie = "theme=dark; path=/; max-age=3600";
Существует также современный Cookie Store API, который позволяет работать с cookie асинхронно и доступен даже внутри Service Worker.
──
2️⃣ Storage API (
localStorage и sessionStorage)Простое синхронное строковое хранилище.
🔸 Недостатки:
— Лимиты около 5–10 MB.
— Хранит только строки.
— Синхронное API может блокировать поток.
🔹 Что лучше всего хранить:
— Настройки UI.
— Лёгкие состояния.
— Временные данные текущей вкладки.
localStorage живет "сколько угодно", а sesionStorage — до закрытия вкладки.window.localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme'));──
3️⃣ indexedDB
Асинхронная объектная база данных в браузере. Позволяет хранить большие и сложные структуры данных.
🔸 Недостатки:
— Сложное нативное API (callbacks, транзакции).
— Требует дополнительных библиотек для комфортной работы.
— Не лучший вариант для огромных бинарных файлов.
🔹 Что лучше всего хранить:
— Структурированные данные.
— Таблицы, списки, каталоги.
— Большие объёмы KV.
Почти все реальные проекты используют библиотеку Dexie.js, которая делает IndexedDB простой и удобной
// Пример с Dexie — самый удобный вариант
import Dexie from 'dexie';
const db = new Dexie('mydb');
db.version(1).stores({ settings: '' });
await db.settings.put('dark', 'theme');
А вот так выглядит ванильный пример (хуже для реальных проектов):
const db = indexedDB.open('mydb', 1);
db.onupgradeneeded = () => db.result.createObjectStore('settings');
db.onsuccess = () => db.result.transaction('settings', 'readwrite')
.objectStore('settings')
.put('dark', 'theme');──
4️⃣ Origin Private File System (OPFS)
Приватная виртуальная файловая система для сайта.
🔸 Недостатки:
— Не подходит для мелких структур.
— Строгие квоты в Safari.
🔹 Что лучше всего хранить:
— Большие файлы: видео, изображения, аудио.
— Потоковые данные.
— ML-модели, WASM-данные.
const root = await navigator.storage.getDirectory();
const file = await root.getFileHandle('data.txt', { create: true });
const stream = await file.createWritable();
await stream.write('Hello OPFS');
await stream.close();
// Читаем файл
const blob = await file.getFile();
const text = await blob.text();
console.log(text); // "Hello OPFS"
──
5️⃣ Cache API
Управляемый HTTP‑кэш (работает и в Window, и в Service Worker).
🔸 Недостатки:
— Ключом является Request → не настоящий KV.
— Может очищаться браузером.
🔹 Что лучше всего хранить:
— Статические ресурсы (HTML, CSS, JS, шрифты).
— API‑ответы (GET), оффлайн‑кэш.
— Большие JSON и данные, выглядящие как «ресурсы».
const cache = await caches.open('kv');
await cache.put('/kv/theme', new Response('"dark"'));
const res = await cache.match('/kv/theme');
console.log(await res.json()); // "dark"⚠️ Здесь мы используем Cache API как KV — это рабочий, но хаковый способ. Лучше использовать IndexedDB или Storage API.
──
💥 Полезно знать
❗️ В пост не поместилось, смотрите продолжение в комментариях 👇
──
#javascript #frontend
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5
🙁 Почему 0.1 + 0.2 !== 0.3?
Сегодня разберём один из самых известных «глюков» в JavaScript. Но важно понять: это происходит не только в JS — почти все языки программирования ведут себя точно так же.
Разберёмся, почему 0.1 + 0.2 выдаёт 0.30000000000000004, какие ещё числа работают неправильно и как с этим жить 👇
──
1️⃣ Проблема не в JavaScript
JS использует числа формата IEEE‑754 double — ровно как и другие языки (Python, Java, C / C++, C#, Go, Swift, Kotlin и т.д.).
То есть в большинстве языков:
Это не баг JS, это фундамент IEEE‑754.
──
2️⃣ Почему так происходит?
Потому что такие числа, как
Машина обрезает дробь до 53 бит точности → получается приближённое значение.
То же самое с
И когда мы складываем два приближения, результат тоже приближённый:
Причём число 0.3 само по себе может быть представленo ближе к точному значению, чем сумма 0.1 + 0.2.
──
3️⃣ С такими числами тоже будет ошибка
Проблема не только в 0.1 и 0.2.
Примеры:
Причина — те же ошибки точности.
──
4️⃣ Какие числа точные, а какие нет?
Точные — только дроби вида k / 2ⁿ (
Все обычные десятичные дроби — неточные (
──
5️⃣ Как жить с этим?
▪️ Использовать EPSILON при сравнении
▪️ Округлять вручную
▪️ Работать в целых числах
(особенно в деньгах)
▪️ Использовать десятичные типы
— Python:
— Java:
— C#:
— JS: библиотеки decimal.js или big.js
Эти типы хранят числа в десятичной, а не двоичной системе → ошибка исчезает.
──
✅ Вывод
Проблема 0.1 + 0.2 — это не баг JavaScript, а свойство всей двоичной плавающей арифметики. Такие ошибки есть почти во всех языках.
Главное — понимать, что происходит, и правильно обходить такие случаи.
──
🙏 Нас уже почти 100 подписчиков! Всем спасибо!
В честь события разыграю телеграм премиум случайному подписчику ☺️
#javascript #frontend
Сегодня разберём один из самых известных «глюков» в JavaScript. Но важно понять: это происходит не только в JS — почти все языки программирования ведут себя точно так же.
Разберёмся, почему 0.1 + 0.2 выдаёт 0.30000000000000004, какие ещё числа работают неправильно и как с этим жить 👇
──
1️⃣ Проблема не в JavaScript
JS использует числа формата IEEE‑754 double — ровно как и другие языки (Python, Java, C / C++, C#, Go, Swift, Kotlin и т.д.).
То есть в большинстве языков:
0.1 + 0.2 === 0.3 // false
Это не баг JS, это фундамент IEEE‑754.
──
2️⃣ Почему так происходит?
Потому что такие числа, как
0.1, 0.2, 0.3 невозможно точно представить в двоичной системе.0.1 в двоичной системе = 0.0001100110011001100… (бесконечная дробь)
Машина обрезает дробь до 53 бит точности → получается приближённое значение.
То же самое с
0.2.И когда мы складываем два приближения, результат тоже приближённый:
0.1 + 0.2 // 0.30000000000000004
Причём число 0.3 само по себе может быть представленo ближе к точному значению, чем сумма 0.1 + 0.2.
──
3️⃣ С такими числами тоже будет ошибка
Проблема не только в 0.1 и 0.2.
Примеры:
0.1 + 0.7 // 0.7999999999999999
0.2 + 0.4 // 0.6000000000000001
0.15 + 0.15 // 0.30000000000000004
0.05 + 0.01 // 0.060000000000000005
Причина — те же ошибки точности.
──
4️⃣ Какие числа точные, а какие нет?
Точные — только дроби вида k / 2ⁿ (
0.5, 0.25, 0.125, 1.5, 3.75)Все обычные десятичные дроби — неточные (
0.1, 0.2, 0.3, 0.05, 1.1, 123.456)──
5️⃣ Как жить с этим?
▪️ Использовать EPSILON при сравнении
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON
▪️ Округлять вручную
Number((0.1 + 0.2).toFixed(2)) // 0.3
▪️ Работать в целых числах
(особенно в деньгах)
(1 + 2) / 10
▪️ Использовать десятичные типы
— Python:
Decimal— Java:
BigDecimal— C#:
decimal— JS: библиотеки decimal.js или big.js
Эти типы хранят числа в десятичной, а не двоичной системе → ошибка исчезает.
──
✅ Вывод
Проблема 0.1 + 0.2 — это не баг JavaScript, а свойство всей двоичной плавающей арифметики. Такие ошибки есть почти во всех языках.
Главное — понимать, что происходит, и правильно обходить такие случаи.
──
🙏 Нас уже почти 100 подписчиков! Всем спасибо!
В честь события разыграю телеграм премиум случайному подписчику ☺️
#javascript #frontend
👍5❤3
😌 Делаем эффект неоновой плазмы на чистом WebGL
Наверняка все слышали о WebGL, но почти никто не писал шейдеры вручную 😁
Сегодня мы разберём простой пример с неоновой плазмой! Никакой библиотечной магии — только хардкор! 🙈
🤔 Для начала давайте разберёмся, что вообще такое WebGL?
По сути, WebGL – это технология для рендеринга 2D и 3D-графики прямо в браузере, используя возможности видеокарты (GPU).
Всё, что нам нужно — это создать обычный
──
1️⃣ Вершинный шейдер
Напишем код для вершинного шейдера – он просто выводит на экран один большой прямоугольник:
──
2️⃣ Фрагментный шейдер
А вот здесь как раз происходит вся "магия" 😊
Мы генерируем плазму, смешивая синусы, косинусы, время и координаты мыши:
──
3️⃣ WebGL-код
Осталось дело за малым: написать в JS компиляцию шейдеров, создать прямоугольник, подготовить uniforms и выполнить рендер анимации в canvas.
──
👉 Весь код в пост не поместился, поэтому смотрите финальный пример на CodePen:
https://codepen.io/webeach/pen/GgZOrre
#javascript #webgl #frontend
Наверняка все слышали о WebGL, но почти никто не писал шейдеры вручную 😁
Сегодня мы разберём простой пример с неоновой плазмой! Никакой библиотечной магии — только хардкор! 🙈
🤔 Для начала давайте разберёмся, что вообще такое WebGL?
По сути, WebGL – это технология для рендеринга 2D и 3D-графики прямо в браузере, используя возможности видеокарты (GPU).
Всё, что нам нужно — это создать обычный
<canvas> элемент, описать шейдеры, скомпилировать их на уровне JS и вывести всё в canvas 👇──
1️⃣ Вершинный шейдер
Напишем код для вершинного шейдера – он просто выводит на экран один большой прямоугольник:
// const vsSource
layout(location = 0) in vec2 a_position;
void main() {
// Позиция вершины. У нас будет всего 1 большой прямоугольник
gl_Position = vec4(a_position, 0.0, 1.0);
}
──
2️⃣ Фрагментный шейдер
А вот здесь как раз происходит вся "магия" 😊
Мы генерируем плазму, смешивая синусы, косинусы, время и координаты мыши:
// const fsSource
precision highp float;
uniform vec2 u_resolution; // Размер экрана
uniform float u_time; // Время в секундах
uniform vec2 u_mouse; // Позиция мыши
out vec4 outColor;
void main() {
// 1. Нормализуем координаты пикселя от 0.0 до 1.0
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// 2. Учитываем пропорции экрана
float aspect = u_resolution.x / u_resolution.y;
uv.x *= aspect;
// 3. Точка интереса (мышь)
vec2 mouse = u_mouse / u_resolution.xy;
mouse.x *= aspect;
// 4. Математика плазмы
float v = 0.0;
// Слой 1: Волны по X
v += sin(uv.x * 10.0 + u_time);
// Слой 2: Волны по Y (чуть медленнее)
v += sin((uv.y * 10.0 + u_time) / 2.0);
// Слой 3: Волны от мыши
float dist = distance(uv, mouse);
v += sin(dist * 15.0 - u_time * 2.0);
// 5. Раскраска (косинусная палитра)
vec3 col = 0.5 + 0.5 * cos(u_time + v + vec3(0, 2, 4));
outColor = vec4(col, 1.0);
}
──
3️⃣ WebGL-код
Осталось дело за малым: написать в JS компиляцию шейдеров, создать прямоугольник, подготовить uniforms и выполнить рендер анимации в canvas.
// Инициализация WebGL
const canvas = document.getElementById('glcanvas');
const gl = canvas.getContext('webgl2');
// Компиляция шейдеров
function createShader(gl, type, source) {
// см. на codepen
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
}
// Создаем геометрию (Один прямоугольник на весь экран)
const positions = new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const positionLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// Подготовка Uniforms
gl.useProgram(program);
const resolutionLoc = gl.getUniformLocation(program, 'u_resolution');
const timeLoc = gl.getUniformLocation(program, 'u_time');
const mouseLoc = gl.getUniformLocation(program, 'u_mouse');
let mouseX = 0, mouseY = 0;
canvas.addEventListener('mousemove', (event) => {
mouseX = event.clientX;
mouseY = canvas.height - event.clientY;
});
// Цикл отрисовки
function render(time) {
// см. на codepen
}
requestAnimationFrame(render);
──
👉 Весь код в пост не поместился, поэтому смотрите финальный пример на CodePen:
https://codepen.io/webeach/pen/GgZOrre
#javascript #webgl #frontend
❤2
🙃 3 полезные практики в Git, о которых почти никто не знает
Обычно наш арсенал использования Git ограничивается стандартными
Ниже приведены 3 неочевидные команды, которые спасут вам кучу времени в нестандартных ситуациях 👇
──
1️⃣ Сброс текущей ветки до
▪️ Ситуация:
Вы работаете в одной ветке с коллегой. Он уже запушил свои правки в
▪️ Проблема:
Обычные способы часто делают "грязно" или больно:
▫️
▫️
▫️
▪️ Решение:
Вместо попыток слить ветки, мы можем просто сбросить нашу локальную ветку до состояния удаленной (origin). Это особенно удобно, если вы хотите "схлопнуть" свои локальные эксперименты в один чистый коммит.
🔹 Вариант А: "Я хочу сохранить свою работу" (Soft)
Делаем так, будто мы только что скачали свежую ветку коллеги, а наши правки просто лежат сверху "незакоммиченные".
Где
🔹 Вариант Б: "Всё сломалось, хочу как на сервере" (Hard)
Если вы запутались в конфликтах или эксперимент не удался, можно жестко привести ветку к состоянию сервера. Внимание: это удалит вашу локальную работу!
──
2️⃣ Игнорирование изменений в отслеживаемых файлах
▪️ Ситуация:
В репозитории лежит общий конфиг (например,
▪️ Проблема:
Git видит изменения и постоянно предлагает их закоммитить.
▫️ Добавить файл в
▫️ Убирать файл из коммита вручную каждый раз — опасно. Рано или поздно вы случайно нажмете
▪️ Решение:
Можно приказать git'у временно "ослепнуть" в отношении конкретного файла. Файл останется в репозитории, но git перестанет замечать любые ваши локальные изменения в нём.
Теперь
▪️ Как вернуть всё назад?
Если структура конфига поменялась на сервере и вам нужно получить обновления, сначала нужно снова включить отслеживание:
──
3️⃣ Автоматическое разрешение повторных конфликтов
▪️ Ситуация:
Вы работаете над долгой задачей и периодически вливаете к себе изменения из main. Или вы начали сложный merge, полчаса решали конфликты, но потом что-то пошло не так (например, тесты упали), и пришлось сделать откат (
▪️ Проблема:
Когда вы снова начнете слияние, Git опять заставит вас вручную решать те же самые конфликты в тех же самых файлах, которые вы уже чинили вчера.
▪️ Решение:
В Git встроена функция с забавным названием rerere (Reuse Recorded Resolution — "повторное использование записанного решения"). Если её включить, Git начнет "запоминать", как именно вы разрулили конфликт в куске кода.
В следующий раз, увидев такой же конфликт, он сам подставит ваше прошлое решение.
Включается один раз и навсегда:
Теперь Git обучается на ваших действиях. Вместо сообщения о конфликте вы всё чаще будете видеть надпись:
──
🤔 А какие полезные практики в git знаете вы?
#frontend #git
Обычно наш арсенал использования Git ограничивается стандартными
git commit, git merge, git push.Ниже приведены 3 неочевидные команды, которые спасут вам кучу времени в нестандартных ситуациях 👇
──
1️⃣ Сброс текущей ветки до
origin▪️ Ситуация:
Вы работаете в одной ветке с коллегой. Он уже запушил свои правки в
origin, и вам нужно подтянуть их к себе, чтобы продолжить работу.▪️ Проблема:
Обычные способы часто делают "грязно" или больно:
▫️
git merge создаст лишний "merge commit" — история превращается в косичку.▫️
git rebase заставит решать конфликты пошагово для каждого вашего локального коммита, если их несколько. Это долго и муторно.▫️
git pull — это по сути тот же merge (или rebase), так что проблема остается.▪️ Решение:
Вместо попыток слить ветки, мы можем просто сбросить нашу локальную ветку до состояния удаленной (origin). Это особенно удобно, если вы хотите "схлопнуть" свои локальные эксперименты в один чистый коммит.
🔹 Вариант А: "Я хочу сохранить свою работу" (Soft)
Делаем так, будто мы только что скачали свежую ветку коллеги, а наши правки просто лежат сверху "незакоммиченные".
git fetch origin
git reset --soft @{u}
Где
@{u} — это сокращение для upstream (текущей ветки на сервере).🔹 Вариант Б: "Всё сломалось, хочу как на сервере" (Hard)
Если вы запутались в конфликтах или эксперимент не удался, можно жестко привести ветку к состоянию сервера. Внимание: это удалит вашу локальную работу!
git fetch origin
git reset --hard @{u}
──
2️⃣ Игнорирование изменений в отслеживаемых файлах
▪️ Ситуация:
В репозитории лежит общий конфиг (например,
config.yaml, .env или docker-compose.yml), который нужен всем. Но на вашей машине (особенно если у вас другая ОС) проект не запускается без локальных правок: нужно поменять порт, путь к базе данных или секретный ключ.▪️ Проблема:
Git видит изменения и постоянно предлагает их закоммитить.
▫️ Добавить файл в
.gitignore нельзя — он уже отслеживается репозиторием, и git проигнорирует это правило.▫️ Убирать файл из коммита вручную каждый раз — опасно. Рано или поздно вы случайно нажмете
git commit -a и отправите свои локальные настройки всем коллегам.▪️ Решение:
Можно приказать git'у временно "ослепнуть" в отношении конкретного файла. Файл останется в репозитории, но git перестанет замечать любые ваши локальные изменения в нём.
git update-index --skip-worktree <путь_к_файлу>
Теперь
git status будет показывать чистоту, даже если вы переписали конфиг полностью.▪️ Как вернуть всё назад?
Если структура конфига поменялась на сервере и вам нужно получить обновления, сначала нужно снова включить отслеживание:
git update-index --no-skip-worktree <путь_к_файлу>
──
3️⃣ Автоматическое разрешение повторных конфликтов
▪️ Ситуация:
Вы работаете над долгой задачей и периодически вливаете к себе изменения из main. Или вы начали сложный merge, полчаса решали конфликты, но потом что-то пошло не так (например, тесты упали), и пришлось сделать откат (
git reset), чтобы попробовать позже.▪️ Проблема:
Когда вы снова начнете слияние, Git опять заставит вас вручную решать те же самые конфликты в тех же самых файлах, которые вы уже чинили вчера.
▪️ Решение:
В Git встроена функция с забавным названием rerere (Reuse Recorded Resolution — "повторное использование записанного решения"). Если её включить, Git начнет "запоминать", как именно вы разрулили конфликт в куске кода.
В следующий раз, увидев такой же конфликт, он сам подставит ваше прошлое решение.
Включается один раз и навсегда:
git config --global rerere.enabled true
Теперь Git обучается на ваших действиях. Вместо сообщения о конфликте вы всё чаще будете видеть надпись:
Resolved 'file.txt' using previous resolution. 😁──
🤔 А какие полезные практики в git знаете вы?
#frontend #git
👍5❤2🔥1
🔥 Представляю всем мою новую библиотеку красивых круговых прогрессов на WebGL
А точнее — сразу две версии:
— 💠 Ванильная JS‑версия: https://github.com/webeach/gl-circular-progress
— ⚛️ React‑обёртка: https://github.com/webeach/gl-circular-progress-react
──
🌈 Варианты прогрессов
Сейчас доступно два эффектных варианта:
— 🌊 Прогресс с эффектом воды (анимированная волна)
— 🔥 Прогресс с эффектом огня (плавные языки пламени)
Все эффекты рендерятся через фрагментные шейдеры, работают плавно и реагируют на изменение прогресса в реальном времени.
💎 Ноль зависимостей — внутри только чистый WebGL и шейдеры. Минимальный размер бандла и максимальная производительность.
──
📦 Установка
──
⚛️ Пример использования в React
──
💠 Пример использования ванильной версии
──
📚 Документация
Полная документация доступна на английском и на русском языках — прямо в README каждого репозитория.
──
🕹 Демо
Посмотреть в действии можно здесь 👇
https://webeach.github.io/gl-circular-progress/
#javascript #frontend #webgl
А точнее — сразу две версии:
— 💠 Ванильная JS‑версия: https://github.com/webeach/gl-circular-progress
— ⚛️ React‑обёртка: https://github.com/webeach/gl-circular-progress-react
──
🌈 Варианты прогрессов
Сейчас доступно два эффектных варианта:
— 🌊 Прогресс с эффектом воды (анимированная волна)
— 🔥 Прогресс с эффектом огня (плавные языки пламени)
Все эффекты рендерятся через фрагментные шейдеры, работают плавно и реагируют на изменение прогресса в реальном времени.
💎 Ноль зависимостей — внутри только чистый WebGL и шейдеры. Минимальный размер бандла и максимальная производительность.
──
📦 Установка
# React версия
npm install @webeach/gl-circular-progress-react
# Ванильная версия
npm install @webeach/gl-circular-progress
──
⚛️ Пример использования в React
import { CircularProgressFire } from '@webeach/gl-circular-progress-react';
function App() {
return (
<div style={{ width: 200, height: 200 }}>
<CircularProgressFire
aria-label="Progress"
options={{
colors: [0xff5a00, 0xff9a00],
progress: 0.5,
speed: 1.5,
thickness: 15,
}}
/>
</div>
);
}──
💠 Пример использования ванильной версии
import { CircularProgressFire } from '@webeach/gl-circular-progress';
// Find canvas element
const canvas = document.getElementById('my-canvas');
// Initialize progress bar
const instance = new CircularProgressFire(canvas, {
colors: [0xff5a00, 0xff9a00],
progress: 0.5, // Initial value
speed: 1.5,
thickness: 15,
});
// Don't forget to clean up when removing the component
// instance.destroy();──
📚 Документация
Полная документация доступна на английском и на русском языках — прямо в README каждого репозитория.
──
🕹 Демо
Посмотреть в действии можно здесь 👇
https://webeach.github.io/gl-circular-progress/
#javascript #frontend #webgl
👍4❤2🔥1🥰1🤔1
🚀 Как сделать виртуальный скролл на чистом CSS?
Все привыкли: если нужно вывести список из 10 000 элементов, мы тянем тяжелые JS-библиотеки вроде
Но в CSS уже есть нативная альтернатива, которая делает рендеринг огромных списков почти мгновенным.
Разбираемся, как работает
──
🤔 Как это работает?
Обычно браузер отрисовывает (расчитывает layout и paint) все элементы на странице сразу после загрузки, даже если они находятся в самом низу, далеко за пределами экрана. Это «вешает» процессор.
Свойство
Браузер буквально «пропускает» отрисовку элемента и расчет его геометрии, пока пользователь до него не доскроллит.
──
😬 Проблема прыгающего скролла
Если браузер не рисует элемент, он считает его высоту равной 0.
Из-за этого высота всей страницы кажется маленькой, а ползунок скролла — огромным.
Как только вы начинаете листать, элементы «распаковываются», страница резко удлиняется, и скроллбар начинает дико скакать.
Чтобы этого не было, нужно использовать свойство: contain-intrinsic-size.
──
✨ Пример
Этого достаточно, чтобы список из 50 000 строк перестал тормозить при загрузке.
──
🤷♂️ Но есть подвох (White Flashing)
Это не полноценная замена JS-виртуализации.
При очень быстром скролле (особенно на слабых устройствах) вы можете увидеть белые пустые места. Браузер просто не успевает «проснуться» и отрисовать контент вовремя.
В JS-библиотеках есть параметр overscan (рендерить заранее с запасом), в CSS его пока нет.
──
👏 Что и когда использовать?
▪️ CSS (
— Длинные статьи, ленты новостей, списки комментариев.
— Когда важна скорость загрузки (First Contentful Paint).
— Когда «белые вспышки» при бешеном скролле не критичны.
▪️ JS (Virtual Scroll):
— Сложные таблицы данных (Data Grids).
— Когда нужен идеальный UX без мелькания пустоты.
— Когда элементы имеют сложную зависимую логику.
──
🌐 Поддержка браузерами
▫️ Chrome 85
▫️ Safari 18
▫️ Firefox 125
──
✅ Вывод
Это не полная замена виртуализации, но для 90% типичных задач (вроде бесконечной ленты) работает отлично.
──
👉 Пример на CodePen:
https://codepen.io/webeach/pen/ZYWmxZa
#frontend #css #javascript
Все привыкли: если нужно вывести список из 10 000 элементов, мы тянем тяжелые JS-библиотеки вроде
react-window или react-virtualized.Но в CSS уже есть нативная альтернатива, которая делает рендеринг огромных списков почти мгновенным.
Разбираемся, как работает
content-visibility и в чем подвох 👇──
🤔 Как это работает?
Обычно браузер отрисовывает (расчитывает layout и paint) все элементы на странице сразу после загрузки, даже если они находятся в самом низу, далеко за пределами экрана. Это «вешает» процессор.
Свойство
content-visibility: auto включает «ленивый рендеринг» для любого HTML-элемента.Браузер буквально «пропускает» отрисовку элемента и расчет его геометрии, пока пользователь до него не доскроллит.
──
😬 Проблема прыгающего скролла
Если браузер не рисует элемент, он считает его высоту равной 0.
Из-за этого высота всей страницы кажется маленькой, а ползунок скролла — огромным.
Как только вы начинаете листать, элементы «распаковываются», страница резко удлиняется, и скроллбар начинает дико скакать.
Чтобы этого не было, нужно использовать свойство: contain-intrinsic-size.
──
✨ Пример
.list-item {
/* 1. Не рисуем элемент, пока он вне экрана */
content-visibility: auto;
/* 2. Задаем примерную высоту (40px) */
/* auto позволяет браузеру запомнить реальный размер после первого рендера */
contain-intrinsic-size: auto 40px;
}Этого достаточно, чтобы список из 50 000 строк перестал тормозить при загрузке.
──
🤷♂️ Но есть подвох (White Flashing)
Это не полноценная замена JS-виртуализации.
При очень быстром скролле (особенно на слабых устройствах) вы можете увидеть белые пустые места. Браузер просто не успевает «проснуться» и отрисовать контент вовремя.
В JS-библиотеках есть параметр overscan (рендерить заранее с запасом), в CSS его пока нет.
──
👏 Что и когда использовать?
▪️ CSS (
content-visibility):— Длинные статьи, ленты новостей, списки комментариев.
— Когда важна скорость загрузки (First Contentful Paint).
— Когда «белые вспышки» при бешеном скролле не критичны.
▪️ JS (Virtual Scroll):
— Сложные таблицы данных (Data Grids).
— Когда нужен идеальный UX без мелькания пустоты.
— Когда элементы имеют сложную зависимую логику.
──
🌐 Поддержка браузерами
content-visibility поддерживается всеми современными браузерами (глобальная поддержка — 91%).▫️ Chrome 85
▫️ Safari 18
▫️ Firefox 125
──
✅ Вывод
content-visibility: auto — это, пожалуй, самый простой способ ускорить рендеринг страницы одной строчкой кода.Это не полная замена виртуализации, но для 90% типичных задач (вроде бесконечной ленты) работает отлично.
──
👉 Пример на CodePen:
https://codepen.io/webeach/pen/ZYWmxZa
#frontend #css #javascript
❤7
🤔 Как правильно клонировать массив в JavaScript?
В этом посте мы поговорим о том, как оптимальнее всего поверхностно склонировать массив.
У нас есть 3 самых популярных способа сделать это:
▫️ через spread →
▫️ через concat →
▫️ через slice →
Мы провели замеры клонирования массива на 100к элементов в 1000 итераций на каждом браузерном движке, используя обычный
Наш первичный код:
Код теста:
Вокруг каждого цикла вызываем
⚙️ Железо: MacOS 15.6 / M4 Pro
──
🏁 Что показали тесты в браузерах:
🔹 Chrome (V8)
▫️ spread → ~1120ms
▫️ concat → ~44ms
▫️ slice → ~41ms
Spread медленнее, чем concat и slice, аж в 27 раз!
Почему? Потому что при клонировании через spread V8 создает итератор, проходится по каждому элементу и добавляет его в новый массив. Slice же использует быструю копию памяти.
🔹 Firefox (SpiderMonkey)
▫️ spread → ~366ms
▫️ concat → ~50ms
▫️ slice → ~18ms
SpiderMonkey справился быстрее всех!
Это значит, что он не копировал данные физически. Он использовал механизм COW (Copy-on-Write) или "ленивые массивы". Он просто создал "ссылку" на старые данные и сказал: "Скопирую по-настоящему только тогда, когда ты захочешь что-то изменить".
🔹 Safari (JavaScriptCore)
▫️ spread → ~300ms
▫️ concat → ~1055ms
▫️ slice → ~1800ms
В Safari (JavaScriptCore) явно есть специальная оптимизация для оператора Spread. Инженеры Apple видят, что весь современный фронтенд написан на спредах, и оптимизировали именно этот путь.
А вот
──
💁♂️ Так какой способ выбрать?
На простых задачах разницы нет никакой, вы просто её не почувствуете.
Но если вы работаете с большими данными (миллионы элементов), то:
▪️ Есть смысл использовать старый-добрый
▪️ Либо написать функцию-утилиту, которая проведет микро-тест при запуске и выберет оптимальный метод для браузера пользователя (пример).
#frontend #javascript
В этом посте мы поговорим о том, как оптимальнее всего поверхностно склонировать массив.
У нас есть 3 самых популярных способа сделать это:
▫️ через spread →
[...arr]▫️ через concat →
[].concat(arr)▫️ через slice →
arr.slice()Мы провели замеры клонирования массива на 100к элементов в 1000 итераций на каждом браузерном движке, используя обычный
console.time().Наш первичный код:
const sourceArray = Array(100_000).fill(0);
// создаем массив на 100к элементов и заполняем "нулями"
Код теста:
// spread
for (let i = 0; i < 1_000; i++) {
([...sourceArray]);
}
// concat
for (let i = 0; i < 1_000; i++) {
[].concat(sourceArray);
}
// slice
for (let i = 0; i < 1_000; i++) {
sourceArray.slice();
}
Вокруг каждого цикла вызываем
console.time() и console.timeEnd().⚙️ Железо: MacOS 15.6 / M4 Pro
──
🏁 Что показали тесты в браузерах:
🔹 Chrome (V8)
▫️ spread → ~1120ms
▫️ concat → ~44ms
▫️ slice → ~41ms
Spread медленнее, чем concat и slice, аж в 27 раз!
Почему? Потому что при клонировании через spread V8 создает итератор, проходится по каждому элементу и добавляет его в новый массив. Slice же использует быструю копию памяти.
🔹 Firefox (SpiderMonkey)
▫️ spread → ~366ms
▫️ concat → ~50ms
▫️ slice → ~18ms
SpiderMonkey справился быстрее всех!
Это значит, что он не копировал данные физически. Он использовал механизм COW (Copy-on-Write) или "ленивые массивы". Он просто создал "ссылку" на старые данные и сказал: "Скопирую по-настоящему только тогда, когда ты захочешь что-то изменить".
🔹 Safari (JavaScriptCore)
▫️ spread → ~300ms
▫️ concat → ~1055ms
▫️ slice → ~1800ms
В Safari (JavaScriptCore) явно есть специальная оптимизация для оператора Spread. Инженеры Apple видят, что весь современный фронтенд написан на спредах, и оптимизировали именно этот путь.
А вот
slice в Safari на больших объемах работает ужасно. Скорее всего, Safari честно аллоцирует новую память и копирует данные, в то время как V8 и Firefox используют хитрости.──
💁♂️ Так какой способ выбрать?
На простых задачах разницы нет никакой, вы просто её не почувствуете.
Но если вы работаете с большими данными (миллионы элементов), то:
▪️ Есть смысл использовать старый-добрый
slice() (быстрее везде, кроме Safari).▪️ Либо написать функцию-утилиту, которая проведет микро-тест при запуске и выберет оптимальный метод для браузера пользователя (пример).
#frontend #javascript
1👍6❤2
🔥 Представляю всем мою новую библиотеку небольшого расширения React
Вдохновился
──
🤔 Зачем это нужно?
Если ты устал от:
▫️
▫️ сложных
▫️ трудной работы с css-переменными при разработке UI-компонентов
…то react-x закрывает эти боли прямо на уровне JSX.
──
✨ Возможности
▪️ Удобная работа с классами
▪️ Работа с inline-стилями теперь проще и читабельнее:
▪️ CSS-переменные больше не "боль"
в стилях:
──
📦 Установка
──
📚 Документация
Полная документация и примеры доступны на английском и на русском языках:
👉 https://github.com/webeach/react-x
#frontend #javascript #react
react-x — небольшая либа, которая прокачивает JSX для работы с CSS-классами, стилями и CSS-переменными без лишнего шума, хаков и кастомных хелперов.Вдохновился
svelte 🙃──
🤔 Зачем это нужно?
Если ты устал от:
▫️
clsx(className, condition && styles.container)▫️ сложных
style объектов▫️ трудной работы с css-переменными при разработке UI-компонентов
…то react-x закрывает эти боли прямо на уровне JSX.
──
✨ Возможности
▪️ Удобная работа с классами
<x.div
class:container
class:disabled={isDisabled}
/>
// или
<x.div
classList={['container', isDisabled && 'disabled']}
/>
▪️ Работа с inline-стилями теперь проще и читабельнее:
<x.div
style:backgroundColor="blue"
style:width={100}
style:height={100}
/>
▪️ CSS-переменные больше не "боль"
<x.div
class:container
var:bg="blue"
var:size="100px"
/>
в стилях:
.container {
background-color: var(--bg);
width: var(--size);
height: var(--size);
}──
📦 Установка
npm install @webeach/react-x
──
📚 Документация
Полная документация и примеры доступны на английском и на русском языках:
👉 https://github.com/webeach/react-x
#frontend #javascript #react
1🔥4
🤔 Почему Math.random() опасен?
Все знают фразу: «не используй Math.random() для безопасности».
Но обычно никто не объясняет почему именно.
Давайте разберем конкретный механизм и реальный сценарий проблемы.
──
💠 Как Math.random() работает под капотом
В большинстве окружений Node.js это движок V8.
И начиная примерно с 2015 года V8 использует PRNG-алгоритм xorshift128+.
Он хранит внутреннее состояние из двух 64-битных чисел:
▫️
▫️
Каждый вызов
1) обновляет состояние
2) возвращает число, построенное на этом состоянии
Упрощённо это выглядит так:
❗️ Math.random() — это не «настоящая случайность», а всего лишь псевдослучайный генератор.
──
🫠 Так где же проблема?
Проблема в том, что если злоумышленник "разгадает":
▫️
▫️
…то он сможет предсказывать все будущие
То есть «рандом» превращается в предсказуемую последовательность.
──
🧩 Разве можно восстановить состояние?
Да. И это самое неприятное.
Если злоумышленнику известны несколько последних значений
Идея такая:
1) мы берем последовательность значений
2) подаем её в солвер
3) просим найти такие
Если солвер находит решение — всё, поток случайных чисел сломан.
──
🔻 Мини-демо
Я сделал небольшой скрипт на python, который предсказывает
👉 https://github.com/webeach-learn/math-random-predictor
Сгенерируйте 5 значений на Node.js:
И вставьте в скрипт
Запустите скрипт и он предскажет будущий
──
😱 Реальный сценарий атаки
Представим типичный функционал сброса пароля на сайте:
Сайт генерирует токен так:
И отправляет пользователю ссылку:
Теперь злоумышленник делает следующее:
1️⃣ отправляет 5 запросов на сброс пароля (на свою почту)
2️⃣ получает 5 токенов, сгенерированных сервером
3️⃣ восстанавливает внутреннее состояние
4️⃣ предсказывает следующие токены, которые сервер сгенерирует уже для других пользователей
В итоге злоумышленник может перехватить чужой сброс пароля и забрать чужой аккаунт.
──
✅ Вывод
Для токенов, паролей, reset-ссылок, session-id и всего «секьюрного» используйте криптографический генератор:
──
⚠️ Важно
Эта информация предоставлена только в образовательных целях — не используйте подобные техники для взлома, эксплуатации уязвимостей или получения доступа к чужим аккаунтам. Используйте это знание, чтобы делать свои системы безопаснее.
#frontend #javascript #security
Все знают фразу: «не используй Math.random() для безопасности».
Но обычно никто не объясняет почему именно.
Давайте разберем конкретный механизм и реальный сценарий проблемы.
──
💠 Как Math.random() работает под капотом
В большинстве окружений Node.js это движок V8.
И начиная примерно с 2015 года V8 использует PRNG-алгоритм xorshift128+.
Он хранит внутреннее состояние из двух 64-битных чисел:
▫️
state0▫️
state1Каждый вызов
Math.random():1) обновляет состояние
2) возвращает число, построенное на этом состоянии
Упрощённо это выглядит так:
let state0 = 1n; // неизвестно
let state1 = 2n; // неизвестно
function xorshift128plus() {
let s1 = state0
let s0 = state1
state0 = s0
s1 ^= (s1 << 23n) & 0xFFFFFFFFFFFFFFFFn
s1 ^= s1 >> 17n
s1 ^= s0
s1 ^= s0 >> 26n
state1 = s1
return (state0 + state1) & 0xFFFFFFFFFFFFFFFFn
}
❗️ Math.random() — это не «настоящая случайность», а всего лишь псевдослучайный генератор.
──
🫠 Так где же проблема?
Проблема в том, что если злоумышленник "разгадает":
▫️
state0▫️
state1…то он сможет предсказывать все будущие
Math.random()То есть «рандом» превращается в предсказуемую последовательность.
──
🧩 Разве можно восстановить состояние?
Да. И это самое неприятное.
Если злоумышленнику известны несколько последних значений
Math.random(), то он может восстановить внутреннее состояние через SMT-солвер (например, Z3 Microsoft Research).Идея такая:
1) мы берем последовательность значений
Math.random()2) подаем её в солвер
3) просим найти такие
state0/state1, которые могли сгенерировать эту последовательностьЕсли солвер находит решение — всё, поток случайных чисел сломан.
──
🔻 Мини-демо
Я сделал небольшой скрипт на python, который предсказывает
Math.random()👉 https://github.com/webeach-learn/math-random-predictor
Сгенерируйте 5 значений на Node.js:
console.log([...Array(5)].map(() => Math.random()))
И вставьте в скрипт
main.py.Запустите скрипт и он предскажет будущий
Math.random() вашего Node.js процесса.──
😱 Реальный сценарий атаки
Представим типичный функционал сброса пароля на сайте:
Сайт генерирует токен так:
const token = Math.random().toString(36).slice(2)
И отправляет пользователю ссылку:
https://some-site.com/reset-password?token=...
Теперь злоумышленник делает следующее:
1️⃣ отправляет 5 запросов на сброс пароля (на свою почту)
2️⃣ получает 5 токенов, сгенерированных сервером
3️⃣ восстанавливает внутреннее состояние
Math.random()const sourceRandom = parseInt(hash, 36) / Math.pow(36, hash.length)
4️⃣ предсказывает следующие токены, которые сервер сгенерирует уже для других пользователей
В итоге злоумышленник может перехватить чужой сброс пароля и забрать чужой аккаунт.
──
✅ Вывод
Для токенов, паролей, reset-ссылок, session-id и всего «секьюрного» используйте криптографический генератор:
import { randomBytes } from 'node:crypto';
const token = randomBytes(32).toString('hex');Math.random() используйте только на клиенте, например, для каких-либо "случайных" значений в UI.──
⚠️ Важно
Эта информация предоставлена только в образовательных целях — не используйте подобные техники для взлома, эксплуатации уязвимостей или получения доступа к чужим аккаунтам. Используйте это знание, чтобы делать свои системы безопаснее.
#frontend #javascript #security
1❤6👍4