Вебыч
126 subscribers
26 photos
2 videos
12 links
Мир фронтенд разработки, и не только!
Download Telegram
В JavaScript не существует литералов "undefined", "NaN" и "Infinity"

Когда вы явно пишете undefined в присвоении или сравнении, вы на самом деле обращаетесь к глобальному свойству undefined, которое содержит значение undefined.

То же самое касается NaN и Infinity — это тоже глобальные свойства:
globalThis.undefinedundefined
globalThis.InfinityInfinity
globalThis.NaNNaN

👉 Немного истории

В старых версиях JavaScript (до ES3) эти свойства можно было переопределить.

Например:
window.undefined = 'hello';
window.Infinity = 'hello';


И это действительно могло сломать сайт, потому что все проверки вроде if (value === undefined) возвращали бы строку 'hello'.

Чтобы получить “чистый” undefined, раньше использовали оператор void:
var undefined = void 0;


📦 Современный стандарт

Начиная с ES3, свойства undefined, NaN и Infinity стали защищёнными: у них дескрипторы имеют writable: false и configurable: false, поэтому их больше нельзя переопределить или удалить.

🧩 Но локальные значения всё ещё возможны

(() => {
const undefined = 'hello';
const Infinity = 100;
const NaN = 'This is NaN';
console.log(undefined, Infinity, -Infinity, NaN);
// 'hello', 100, -100, 'This is NaN'
})();


(() => {
function undefined(Infinity, NaN) {
console.log(Infinity, NaN);
}

undefined('foo', 'bar');
// 'foo', 'bar'
})();



💡 В общем, undefined, NaN и Infinity — не магия, а просто глобальные свойства, которые выглядят как литералы, но на деле являются обычными значениями с защитой от изменений.

#javascript #frontend
52😁1🤔1
В JavaScript существует «минус ноль» (-0) 😅

Да, это не шутка — в JavaScript реально есть -0. Это не баг, а особенность стандарта IEEE-754, на котором основаны все числа с плавающей запятой.

В JavaScript есть два нуля: +0 и -0. Математически они равны, но в некоторых ситуациях ведут себя по-разному 👇

📘 Примеры
+0 === -0true
Object.is(+0, -0)false

А вот при делении всё иначе:
1 / 0Infinity
1 / -0-Infinity

Минус ноль может появляться при:
— округлении отрицательных дробей (Math.ceil(-0.1)-0),
— делении на отрицательное бесконечно малое (1 / -Infinity-0),
— вычислениях, где результат стремится к нулю с отрицательной стороны.

🔍 Как проверить на -0?

function isNegativeZero(value) {
return 1 / value === -Infinity;
}

Или просто:
Object.is(value, -0);


Но будь аккуратен — некоторые линтеры могут удалить знак «минус» перед нулём, посчитав его лишним.

🚀 Где реально может пригодиться -0

Если ты работаешь с векторами, координатами или графикой, -0 может пригодиться для сохранения направления оси при нулевом значении. Например, при нормализации вектора [-0, 0] можно сохранить информацию, что движение шло «влево», даже если длина вектора стала равна нулю.

const v = [-0, 0];
console.log(Math.sign(v[0])); // -0 → направление влево


Это важно в физике, анимациях и симуляциях, где знак направления сохраняет контекст, даже если само значение обнулилось.

⚠️ Ещё один момент

Если вдруг вы используете Object.is для сравнения каких-либо динамических значений, и при этом где-то выше по цепочке логики происходит округление через Math.ceil, то есть риск, что сравнение сработает некорректно — из-за того, что Math.ceil(-0.1) возвращает -0, а не +0. Однако в классических задачах вероятность столкнуться с таким сценарием крайне мала.

🎯 В целом, -0 редко встречается в повседневном коде, но если ты работаешь с низкоуровневой математикой, числовыми симуляциями или движками анимации — стоит помнить, что он существует 😉

#javascript #frontend
3👍21🤔1
Как сайты могут узнать, авторизованы ли вы на каком-то ресурсе? 😨

Да, это реально возможно — и это не баг браузера, а логичный побочный эффект работы веба.

Такой механизм называется "side‑channel" — авторизационный фингерпринтинг (через редиректы / favicon)

🛠 Как это работает:

Для проверки авторизации достаточно:
— URL целевого ресурса, который делает редирект в случае успешного входа
— Ссылки на картинку (обычно favicon), куда произойдёт редирект, если вы авторизованы

📺 Пример с YouTube:

const img = new Image();
img.referrerPolicy = 'no-referrer';

img.onload = () => console.log('Вы авторизованы на YouTube');
img.onerror = () => console.log('Вы не авторизованы на YouTube');

img.src = 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Ffavicon.ico&uilel=3&hl=en&service=youtube';


🔗 Пример с VK:

const img = new Image();
img.referrerPolicy = 'no-referrer';

img.onload = () => console.log('Вы авторизованы в VK');
img.onerror = () => console.log('Вы не авторизованы в VK');

img.src = 'https://vk.com/login?u=2&to=ZmF2aWNvbi5pY28-';


(в этом URL to=ZmF2aWNvbi5pY28- — это закодированная ссылка на favicon.ico, поэтому при наличии сессии будет редирект и картинка загрузится)

📌 Что происходит:
Если вы авторизованы, произойдёт редирект на favicon.ico, и браузер успешно загрузит картинку → сработает onload.
— Если нет, редиректа не будет, и картинка не загрузится → сработает onerror.

❗️ Почему это плохо
Это позволяет сторонним сайтам определить, где вы залогинены, без вашего ведома. Например, можно собрать данные вроде:
— Авторизованы ли вы в YouTube, Facebook, VK, Wikipedia и т. д.
— Используете ли вы учётку Google или VK
— Какие сервисы у вас активны прямо сейчас

Таким образом, можно создавать ваш цифровой профиль и использовать его для таргетинга, отслеживания или других нежелательных действий. 😬

Современные браузеры частично блокируют такие сценарии (CORS, контроль реферера, приватные режимы), но не всегда и не везде — особенно если ресурсы используют редиректы и открытые пути к иконкам.

⚠️ Важно
Эта информация предоставлена только в образовательных целях — не используйте подобные техники для отслеживания пользователей. Это может нарушать политику конфиденциальности и законы о защите данных.

#javascript #frontend #websecurity
12👍2🤯1
🤔 В чём отличие interface и type в TypeScript на самом деле?

Этот вопрос часто задают на собеседованиях — и, что удивительно, большинство кандидатов и даже интервьюеров не знают главного различия.

──

Базовые отличия:

1️⃣ interface с одинаковым именем склеивается (merge), но только в рамках одного контекста (scope).
type, наоборот, выдаст ошибку при попытке повторного объявления.

2️⃣ interface может наследовать другие интерфейсы, но и типы тоже может наследовать — только если эти типы имеют объектную форму и не содержат объединений (union),
а type объединяет и типы, и интерфейсы через пересечение (&).

3️⃣ interface описывает только объекты и функции,
а type может описывать любой тип, включая примитивы, объединения (|) и пересечения (&).

──

⚙️ Менее очевидные различия:

4️⃣ interface можно использовать с implements (в классах),
он также может описывать new().
type в implements работает ограниченно — описывать new() нельзя.

5️⃣ type поддерживает объединения (union), а interface — нет.

type Entity = 
| { type: 'user'; name: string }
| { type: 'post'; title: string };


──

🔥 Самое важное различие:

6️⃣ interface — это статическая структура,
а typeдинамическая.

В interface нельзя описать вычисляемые ключи,
он требует фиксированную структуру.

А type позволяет создавать динамические типы:

type User = { id: number; name: string };
type ReadonlyUser = { [K in keyof User]: User[K] };


Это называется mapped type, и оно возможно только с type.

Если попытаться сделать то же самое через интерфейс:

interface ReadonlyUser {
[K in keyof User]: User[K]; // Ошибка
}


TypeScript сообщит: "A mapped type may not declare properties or methods".


👇 А это разве не динамический interface?

interface Dictionary {
[key: string]: number;
}


Нет, это не настоящая динамика, а просто маска для любых свойств — без вычислений и зависимостей от других типов.

Это называется index signature (индексная подпись) — способ сказать, что объект может содержать свойства с любыми строковыми именами определённого типа.

──

⚡️ Производительность:

7️⃣ Из-за вычислений type может компилироваться медленнее,
поскольку компилятору нужно разрешать больше зависимостей между типами.

──

Итог:

▪️ interface — для описания структур и контрактов классов.
▪️ type — для гибких, вычисляемых и комбинированных типов.

──

👉 Сохраняй, чтобы не забыть и удивить любого интервьювера на собеседовании. 😁

#javascript #typescript #frontend
1
😅 В JavaScript можно «прервать» любой блок кода!

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

Но мало кто знает, что похожее можно сделать и с обычными блоками.

──

Вспомним, как прерывается цикл:
for (let i = 0; i < 10; i++) {
if (i === 2) {
break;
}
}


На самом деле ключевое слово break не привязано к циклам.

Оно работает в любых блоках, а то, что мы привыкли видеть его в for или while — лишь следствие того, что эти конструкции сами используют блоки.

──

🧩 Можно прервать даже if:
let x = 1;

labelName: if (x === 1) {
console.log('Hello');

break labelName;

console.log('World'); // этот код не сработает
}


Почему это работает? 👆
Потому что if содержит тот же самый блок { ... }, как и цикл.

──

💡 А можно прервать и просто блок кода:
const x = 1;

labelName: {
console.log('Hello');

if (x === 1) {
break labelName;
}

console.log('World'); // этот код не сработает
}


──

⚠️ На практике такое лучше не использовать, чтобы не усложнять читаемость кода.

Но теперь вы знаете чуть больше про JavaScript — и можете блеснуть этим фактом на собеседовании 😉

#javascript #frontend
👍31
🤔 А вы знали, что в JavaScript существует ограничение на количество аргументов в функции?

Несмотря на то, что в спецификации JavaScript нет явного лимита, на практике он существует — и определяется он конкретным движком, на котором выполняется ваш код.

Вот что мне удалось выяснить 👇

──

🔹 JavaScriptCore
Где используется: Safari
Лимит аргументов: 65 536

Почему именно столько?
— Аргументы кодируются в 16-битное поле, а 16 бит дают максимум 2¹⁶ = 65 536.
Движки любят степени двойки — это быстро, удобно и хорошо ложится на архитектуру процессоров.

🔹 SpiderMonkey
Где используется: Firefox
Лимит аргументов: 500 000

Значение зашито в движок как фиксированная константа. Работает строго.

🔹 V8
Где используется: Chrome, Node.js, Opera и др.
Лимит аргументов: плавающий ~119 000 (но не более 120 000)

В V8 этот лимит — не программный (как в Firefox) и не архитектурный (как в Safari), а физический. Он напрямую упирается в размер стека вызовов (call stack), который у V8 весьма мал (часто около 1 МБ).

Когда вы пишете fn(...params), V8 пытается поместить каждый элемент из params в стек как отдельный аргумент.

Как раз ~119 000 аргументов (каждый из которых — по сути, указатель) и «съедают» весь этот мегабайт.

Но ещё, в исходниках есть такое:
constexpr int kMaxFunctionArguments = 120000;


👆 Это «запасной» программный лимит, но на практике до него дело почти никогда не доходит — физический стек заканчивается раньше.

──

📌 Пример ошибки

function test(...args) {
console.log(args.length);
}

test(...Array(120_000)); // Chrome → RangeError: Maximum call stack size exceeded

test(...Array(500_001)); // Firefox → RangeError: too many function arguments


──

⚠️ Где это может неожиданно «выстрелить»?

Самый частый кейс — это:
arr.push(...data)


Если data слишком большая, вы рискуете упасть в ошибку — особенно в Safari, где лимит самый маленький.

──

🛡 Как обезопаситься?

Если данных потенциально много, рассмотрите альтернативы:

1) Использовать arr.concat(data)
Но учтите, что он не мутирует исходный массив.

2) Либо писать «безопасный пуш» — разбивать данные на чанки:

const CHUNK_MAX_SIZE = 50_000;

function safePush(arr, data) {
const dataCopy = data.slice();

while (dataCopy.length > 0) {
const chunk = dataCopy.splice(0, CHUNK_MAX_SIZE);
arr.push(...chunk);
}
}


#javascript #frontend
3👍3
😊 Расширяем возможности CSS с помощью ворклетов!

В этом небольшом уроке мы добавим в CSS возможность отрисовывать паттерн «клетки» с настройкой цветов и размеров.

Раньше подобное делали через хаки с градиентами, но теперь сделаем это чисто и нативно — с помощью CSS Painting API 🎨

──

💡 Что такое CSS Paint Worklet простыми словами?

Это мини-скрипт на JavaScript, который браузер запускает прямо внутри CSS, чтобы рисовать динамические фоны, градиенты, паттерны и многое другое.

Вместо PNG или SVG вы просто говорите браузеру:
«Эй, нарисуй фон вот по такому алгоритму!»

И он сам всё рисует — быстро, нативно и без лагов ⚡️

──

🧩 Как это работает

1️⃣ Мы создаём отдельный класс с методом paint, который принимает три аргумента:
context — объект типа PaintRenderingContext2D, урезанная версия стандартного CanvasRenderingContext2D. Через него мы реализуем всю отрисовку (например, fillRect, arc, fillStyle, stroke и т.д.).
geom — содержит размеры области, куда выполняется отрисовка (width, height).
properties — коллекция всех CSS-переменных, доступных ворклету.

2️⃣ Этот класс обязательно нужно зарегистрировать через глобальную функцию registerPaint('имя', Класс).
Это сообщает браузеру, что теперь доступна новая функция отрисовки с именем 'имя', которую можно вызвать в CSS через background-image: paint(имя);.

3️⃣ После этого нужно подключить сам модуль ворклета:
CSS.paintWorklet.addModule('путь_к_файлу.js');
Важно, чтобы регистрация класса и вызов registerPaint находились в одном файле, потому что ворклет работает в изолированном окружении, похожем на Web Worker.

Таким образом, ворклет — это мини-рендерер, который выполняется параллельно основному потоку, и браузер сам решает, когда и как его вызывать. Это делает API невероятно эффективным и гибким.

──

🧠 Реализация ворклета

// простая утилита для получение CSS переменной и нормализации её значения
function getProperty(properties, key) {
const value = properties.get(key);
return value ? value.toString().trim() : null;
}

// Наш класс для отрисовки
class CheckerPainter {
static get inputProperties() {
// описываем какие CSS переменные мы принимаем на вход
return [
'--checker-color-1',
'--checker-color-2',
'--checker-cell-width',
'--checker-cell-height',
];
}

// Вся "магия" происходит в этом методе
paint(ctx, geom, properties) {
const color1 =
getProperty(properties, '--checker-color-1') || '#000000';
const color2 =
getProperty(properties, '--checker-color-2') || '#FFFFFF';

const cellWidthRaw =
getProperty(properties, '--checker-cell-width') || '24px';
const cellHeightRaw =
getProperty(properties, '--checker-cell-height') || '24px';

// Простейший парсинг числа, px нам не важно
const cellWidth = parseFloat(cellWidthRaw) || 24;
const cellHeight = parseFloat(cellHeightRaw) || 24;

const cols = Math.ceil(geom.width / cellWidth);
const rows = Math.ceil(geom.height / cellHeight);

for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
ctx.fillStyle = (x + y) % 2 === 0 ? color1 : color2;
ctx.fillRect(
x * cellWidth,
y * cellHeight,
cellWidth,
cellHeight
);
}
}
}
}

registerPaint('checker', CheckerPainter);


Подключаем модуль:
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('checker-painter.js');
}


──

🎨 Применяем в CSS

<div class="box"></div>


.box {
--checker-color-1: #D394C8;
--checker-color-2: #60C7EF;
--checker-cell-width: 32px;
--checker-cell-height: 32px;

background-image: paint(checker);
width: 640px;
height: 480px;
}


Теперь у нас появляется красивая сине-розовая клетка на весь контейнер 💖

──

Добавим анимацию

❗️ В пост не поместилось, смотрите в комментариях 👇

──

👉 Финальный пример на CodeSandbox:
https://codesandbox.io/p/sandbox/8r4rnn

#html #css #javascript #frontend
👍21
😢 Оператор delete в JavaScript замедляет ваш код!

Многие разработчики не знают, что 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 может сломать ваш сайт или приложение

Многие не знают, но обращение к 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, о которых должен знать каждый!

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 их не поддерживает.

▫️ В объектах → удаляются
▫️ В массивах → превращаются в null

JSON.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).
— Минимальные настройки, нужные серверу.

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 и т.д.).

То есть в большинстве языков:
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
👍53
😌 Делаем эффект неоновой плазмы на чистом WebGL

Наверняка все слышали о 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
🔥 Представляю всем мою новую библиотеку красивых круговых прогрессов на 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
👍42🔥1🥰1🤔1
🚀 Как сделать виртуальный скролл на чистом CSS?

Все привыкли: если нужно вывести список из 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 → [...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👍62
🔥 Представляю всем мою новую библиотеку небольшого расширения 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-битных чисел:

▫️ 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
16👍4