Ghostly Frontend
رفتن به کانال در Telegram
Погружаемся в мир Frontend: задачи, фишки, библиотеки и террабайты полезного материала. Сотрудничество: @heywan_n1 Цены: @heywan_media
نمایش بیشتر2 518
مشترکین
-124 ساعت
-97 روز
-3130 روز
آرشیو پست ها
2 519
👩💻 Фишка: structuredClone() — глубокое копирование объектов
Метод structuredClone() позволяет создавать глубокие копии объектов, включая вложенные структуры, даты, Map, Set, массивы и даже бинарные данные. Решает проблемы JSON.parse(JSON.stringify()) и ручного копирования.
// Базовое копирование объектов const original = { name: 'Анна', age: 28, hobbies: ['рисование', 'чтение'], address: { city: 'Москва', street: 'Тверская' } }; const copy = structuredClone(original); copy.name = 'Мария'; copy.hobbies.push('йога'); copy.address.city = 'СПб'; console.log(original.name); // → "Анна" (не изменилось) console.log(original.hobbies); // → ['рисование', 'чтение'] (не изменилось) console.log(original.address.city); // → "Москва" (не изменилось) // Копирование специальных типов const complexData = { date: new Date('2024-03-15'), map: new Map([['key1', 'value1'], ['key2', 'value2']]), set: new Set([1, 2, 3, 4]), regex: /test/gi, typedArray: new Uint8Array([1, 2, 3]), error: new Error('Тестовая ошибка') }; const complexCopy = structuredClone(complexData); console.log(complexCopy.date instanceof Date); // → true (сохранился тип) console.log(complexCopy.map instanceof Map); // → true console.log(complexCopy.set instanceof Set); // → true console.log(complexCopy.typedArray); // → Uint8Array [1, 2, 3] // Сравнение с JSON.stringify const data = { name: 'Иван', birth: new Date(1990, 1, 1), tags: ['js', 'dev'] }; // JSON метод (не сохраняет типы) const jsonCopy = JSON.parse(JSON.stringify(data)); console.log(jsonCopy.birth); // → строка, а не Date! // structuredClone (сохраняет типы) const cloneCopy = structuredClone(data); console.log(cloneCopy.birth instanceof Date); // → true // Копирование циклических ссылок (ошибка!) const circular = { name: 'Объект' }; circular.self = circular; // structuredClone(circular); // → TypeError: Maximum call stack size exceeded // Практический пример: история изменений class HistoryManager { constructor(initialState) { this.history = [structuredClone(initialState)]; this.currentIndex = 0; } push(state) { const newHistory = this.history.slice(0, this.currentIndex + 1); newHistory.push(structuredClone(state)); this.history = newHistory; this.currentIndex++; } undo() { if (this.currentIndex > 0) { this.currentIndex--; return structuredClone(this.history[this.currentIndex]); } return null; } redo() { if (this.currentIndex < this.history.length - 1) { this.currentIndex++; return structuredClone(this.history[this.currentIndex]); } return null; } } // Использование const initialState = { user: { name: 'Анна', settings: { theme: 'dark' } }, todos: ['купить хлеб'] }; const history = new HistoryManager(initialState); // Изменяем состояние const newState = structuredClone(initialState); newState.todos.push('позвонить маме'); history.push(newState); // Возвращаемся назад const previousState = history.undo(); console.log(previousState.todos); // → ['купить хлеб'] // Копирование для реактивных систем const appState = { users: [ { id: 1, name: 'Петр', profile: { age: 32 } }, { id: 2, name: 'Елена', profile: { age: 28 } } ], meta: { lastUpdate: new Date(), version: '1.0.0' } }; // Обновляем без мутации function updateUserName(state, userId, newName) { const newState = structuredClone(state); const user = newState.users.find(u => u.id === userId); if (user) user.name = newName; newState.meta.lastUpdate = new Date(); return newState; } const updatedState = updateUserName(appState, 1, 'Петр Петрович'); console.log(appState.users[0].name); // → "Петр" (оригинал не изменился) console.log(updatedState.users[0].name); // → "Петр Петрович"📌 Преимущества: — Глубокое копирование любой сложности за одну операцию — Сохранение типов (Date, Map, Set, RegExp и др.) — Корректная обработка бинарных данных — Встроенный метод без зависимостей — Производительнее ручных решений — Идеально для работы с состоянием в React/Vue 🧡 Ghostly Frontend | #фишки
2 519
Tab Bar Interaction — система вкладок с анимацией
Доступен выбор светлой и темной темы, написана на JS, HTML и CSS.
➡️ Ссылка на код
🧡 Ghostly Frontend | #codepen
2 519
👩💻 Выбираем стек для веб-сайта: SSR, SSG, CSR и другие
В современном мире веб-разработки выбор стратегии рендеринга сайта является одним из ключевых решений, определяющих его производительность, оптимизацию для поисковых систем (SEO) и пользовательский опыт.
В статье рассматриваются основные типы рендеринга: их преимущества, недостатки, влияние на SEO, производительность и многое другое.
➡️ Ссылка на статью
🧡 Ghostly Frontend | #статьи
2 519
👩💻 Фишка: AbortController — отмена асинхронных операций
AbortController предоставляет стандартизированный способ отмены асинхронных операций в JavaScript. Особенно полезен для отмены fetch-запросов, таймеров и обработчиков событий при уходе пользователя со страницы.
// Базовый пример с fetch запросом
const controller = new AbortController();
const signal = controller.signal;
// Функция для загрузки данных с возможностью отмены
async function fetchUserData(userId) {
try {
const response = await fetch(/api/users/${userId}, { signal });
const data = await response.json();
console.log('Данные пользователя:', data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Запрос был отменен пользователем');
} else {
console.error('Ошибка загрузки:', error);
}
}
}
// Запускаем запрос
fetchUserData(42);
// Отменяем запрос через 100мс
setTimeout(() => {
controller.abort();
console.log('Запрос отменен');
}, 100);
// Множественные запросы с одним контроллером
async function loadDashboard() {
const controller = new AbortController();
const { signal } = controller;
// Запускаем параллельные запросы
const requests = [
fetch('/api/user', { signal }),
fetch('/api/posts', { signal }),
fetch('/api/comments', { signal })
];
// Таймаут для всех запросов
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const results = await Promise.all(requests);
clearTimeout(timeoutId);
console.log('Все данные загружены');
} catch (error) {
if (error.name === 'AbortError') {
console.log('Загрузка прервана по таймауту');
}
}
}
// Отмена при уходе со страницы
function setupCancellableOperation() {
const controller = new AbortController();
// Отмена при уходе
window.addEventListener('beforeunload', () => {
controller.abort();
});
return controller.signal;
}
// Практический пример: поиск с автодополнением
class SearchWithCancel {
constructor() {
this.currentController = null;
}
async search(query) {
// Отменяем предыдущий поиск
if (this.currentController) {
this.currentController.abort();
}
// Создаем новый контроллер
this.currentController = new AbortController();
const { signal } = this.currentController;
try {
const response = await fetch(`/api/search?q=${query}`, { signal });
const results = await response.json();
return results;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Поиск отменен (новый запрос)');
return null;
}
throw error;
}
}
}
// Использование
const searcher = new SearchWithCancel();
// Быстро набираем текст
searcher.search('jav'); // Отменится
searcher.search('java'); // Отменится
searcher.search('javascript'); // Выполнится
// Отмена таймера
function cancellableTimer(ms) {
const controller = new AbortController();
const { signal } = controller;
const promise = new Promise((resolve, reject) => {
const timeout = setTimeout(resolve, ms);
signal.addEventListener('abort', () => {
clearTimeout(timeout);
reject(new DOMException('Timer aborted', 'AbortError'));
});
});
return { promise, controller };
}
// Использование
const { promise, controller } = cancellableTimer(5000);
promise.then(() => console.log('Таймер сработал'));
// Отменяем через 2 секунды
setTimeout(() => controller.abort(), 2000);
📌 Преимущества:
— Стандартизированный механизм отмены
— Чистая обработка ошибок через AbortError
— Возможность отмены нескольких операций одновременно
— Интеграция с fetch API "из коробки"
— Полезно для предотвращения race conditions
🧡 Ghostly Frontend | #фишки2 519
Анимированные 3D карточки
Карточки с анимацией при наведении и возможностью изменения цвета
➡️ Ссылка на код
🧡 Ghostly Frontend | #codepen
2 519
👩💻 Новые реактивные формы в Angular: Signal Forms API
Статья от фронтенд-разраба из Т-Банка, который расскажет, как с помощью сигналов теперь можно быстро создать типобезопасную форму, настроить валидацию с условными правилами, гибко управлять состояниями полей и легко встраивать кастомные компоненты-контролы.
➡️ Читать статью
🧡 Ghostly Frontend | #статьи
2 519
👩💻 Фишка: Intl.ListFormat — форматирование списков для разных языков
Intl.ListFormat — это встроенный объект JavaScript для форматирования списков с учетом правил языка. Он автоматически добавляет правильные соединительные элементы (союзы, запятые) в зависимости от локали.
// Создание форматтера для разных языков
const fruits = ['яблоко', 'банан', 'апельсин', 'груша'];
// Русское форматирование
const ruList = new Intl.ListFormat('ru', {
style: 'long', // long, short, narrow
type: 'conjunction' // conjunction (и), disjunction (или), unit
});
console.log(ruList.format(fruits));
// → "яблоко, банан, апельсин и груша"
// Английское форматирование
const enList = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction'
});
console.log(enList.format(fruits));
// → "apple, banana, orange, and pear"
// Форматирование с союзом "или" (дизъюнкция)
const orList = new Intl.ListFormat('ru', {
style: 'long',
type: 'disjunction'
});
const options = ['чай', 'кофе', 'сок'];
console.log(orList.format(options));
// → "чай, кофе или сок"
// Короткий стиль для компактного отображения
const shortList = new Intl.ListFormat('en', {
style: 'short',
type: 'conjunction'
});
console.log(shortList.format(['HTML', 'CSS', 'JavaScript']));
// → "HTML, CSS, & JavaScript"
// Для языков с разными правилами
const deList = new Intl.ListFormat('de', {
style: 'long',
type: 'conjunction'
});
console.log(deList.format(['Montag', 'Dienstag', 'Mittwoch']));
// → "Montag, Dienstag und Mittwoch"
// Динамическое форматирование в UI
function formatIngredients(ingredients, lang = 'ru') {
const formatter = new Intl.ListFormat(lang, {
style: 'long',
type: 'conjunction'
});
return formatter.format(ingredients);
}
const pizzaToppings = ['сыр', 'помидоры', 'грибы', 'оливки'];
console.log(Пицца с: ${formatIngredients(pizzaToppings)});
// → "Пицца с: сыр, помидоры, грибы и оливки"
// Работа с числами
const formatter = new Intl.ListFormat('ru', {
style: 'long',
type: 'conjunction'
});
const coordinates = ['X: 10', 'Y: 20', 'Z: 30'];
console.log(formatter.format(coordinates));
// → "X: 10, Y: 20 и Z: 30"
// Сравнение стилей
const colors = ['красный', 'синий', 'зеленый'];
const long = new Intl.ListFormat('ru', { style: 'long' });
const short = new Intl.ListFormat('ru', { style: 'short' });
const narrow = new Intl.ListFormat('ru', { style: 'narrow' });
console.log(long.format(colors)); // → "красный, синий и зеленый"
console.log(short.format(colors)); // → "красный, синий и зеленый" (может совпадать)
console.log(narrow.format(colors)); // → "красный, синий, зеленый"
📌 Преимущества:
— Автоматическая локализация списков
— Правильные грамматические конструкции для каждого языка
— Поддержка разных стилей длинный, короткий, узкий)
— Работа с союзами "и" (конъюнкция) и "или" (дизъюнкция)
— Идеально для многоязычных интерфейсов
— Учитывает культурные особенности форматирования
🧡 Ghostly Frontend | #фишки2 519
Как разработчик решил параллельно найму пилить свои бизнес-проекты с нулевым опытом: дневник с передовой
Меня зовут Александр Торбек, И я попал в день сурка: код писать умею, зарплата стабильная. Но в заднице зудит ощущение катастрофического застоя.
Поэтому я сделал глупейшую вещь — начал разрабатывать продукты. Без связей, плана и стратегии. В блоге буду фиксировать:
— идеи (и почему 90% из них — говно собаки)
— что сделал, сколько заработал
— мысли айтишника, который впервые думает как продакт, а не как тупой исполнитель
Я хочу пройти весь путь от основателя продукта до продажника. И выяснить, смогу ли без бизнес-бэкграунда выйти на уровень дядек в элитных пиджаках.
Если тоже хотите создавать свои продукты — посмотрите, как я набиваю шишки первым: @atorbek_it
2 519
Анимированная панель навигации
Простая и минималистичная панель, сделана на SCSS, TypeScript и SVG.
➡️ Ссылка на код
🧡 Ghostly Frontend | #codepen
2 519
👩💻 Cards with inverted border-radius — Карточки товаров с интересным решением для кнопки.
Сделан на: SCSS
➡️ Код тут
🧡 Ghostly Frontend | #codepen
2 519
👩💻 Фишка: Async Iterator и for-await-of — итерация по асинхронным данным
Асинхронные итераторы и цикл for-await-of позволяют последовательно обрабатывать асинхронные данные, такие как стримы, пагинированные API или события. Идеально для работы с постепенно поступающими данными.
// Асинхронный генератор для пагинированных данных
async function* fetchPaginatedData(url, limit = 3) {
let page = 1;
while (page <= limit) {
// Имитация асинхронного запроса
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items; // Возвращаем данные текущей страницы
page++;
// Останавливаемся если данных больше нет
if (!data.hasNextPage) break;
}
}
// Использование с for-await-of
(async () => {
console.log('Начинаем загрузку данных...');
try {
for await (const items of fetchPaginatedData('/api/users')) {
console.log(`Получено ${items.length} записей`);
items.forEach(user => console.log(`- ${user.name}`));
}
} catch (error) {
console.error('Ошибка загрузки:', error);
}
console.log('Загрузка завершена');
})();
// Асинхронный итератор для стрима
async function* createAsyncStream(source, chunkSize = 10) {
let buffer = [];
for (const item of source) {
buffer.push(item);
if (buffer.length >= chunkSize) {
yield buffer; // Возвращаем накопленные данные
buffer = []; // Очищаем буфер
}
}
// Возвращаем остаток
if (buffer.length > 0) {
yield buffer;
}
}
// Пример с сенсорными данными
async function processSensorData() {
const sensor = {
async *Symbol.asyncIterator {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
yield { temperature: 20 + Math.random() * 5, timestamp: Date.now() };
}
}
};
const readings = [];
for await (const data of sensor) {
console.log(`Температура: ${data.temperature.toFixed(1)}°C`);
readings.push(data);
}
console.log(`Всего получено: ${readings.length} показаний`);
}
// Обработка нескольких асинхронных источников
async function* mergeAsyncStreams(...streams) {
const promises = streams.map(async function* (stream) {
for await (const item of stream) {
yield item;
}
});
for (const promise of promises) {
for await (const item of promise) {
yield item;
}
}
}
// Практический пример: логирование в реальном времени
async function monitorLogs() {
const logStream = {
async *Symbol.asyncIterator {
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield [${new Date().toISOString()}] System heartbeat;
}
}
};
let count = 0;
for await (const log of logStream) {
console.log(log);
count++;
if (count >= 5) break; // Останавливаемся после 5 логов
}
}
📌 Преимущества:
— Работа с постепенно поступающими данными
— Обработка бесконечных стримов
— Эффективное потребление памяти
— Чистая обработка асинхронных последовательностей
— Интеграция с современными API (fetch streams, WebSockets)
🧡 Ghostly Frontend | #фишки2 519
⚡️ Писать код руками больше не нужно!
Привет. Меня зовут Кирилл. Я создаю топовые подборки с уроками по AI и вайбкодингу:
— 60+ промптов для дебага: поиск ошибок, оптимизация кода
— 40 бесплатных курсов по вайбкодингу
— 150 гайдов, как использовать ChatGPT, Claude Code, Antigravity, Cursor, Perplexity, Lovable
— 100 готовых модулей: авторизация, админки, логирование, тесты, i18n
— 37 MCP серверов: дизайн, разработка, Тесты/QA, деплой
Всего 10 минут в день на канале и ты начнешь писать код на 85% быстрее и сможешь за пару дней закрывать спринты.
Подписывайся, чтобы получать подборки каждую неделю!
2 519
🖥 JSFiddle — онлайн-редактор для тестирования и обмена frontend-кодом
JSFiddle — это простая и быстрая песочница для написания HTML, CSS и JavaScript прямо в браузере. Подходит для отладки, демонстрации решений, экспериментов и обмена кодом с другими разработчиками.
Вы можете подключать сторонние библиотеки (jQuery, Vue, React и др.), сохранять свои «фиддлы» и делиться ссылками. Отлично подойдёт как для новичков, так и для менторов и тех, кто участвует в код-ревью.⛓️ Ссылка на ресурс 🧡 Ghostly Frontend | #ресурсы
2 519
В IT хватает тех, кто пересказывает чужие статьи.
Но есть те, кто пишет изнутри системы.
NeuroNinja 🥷🏻 - авторский канал инженера СберТехнологий, который показывает, что происходит по ту сторону интерфейса.
В канале вы найдете:
🟢 Разборы реальных кейсов из практики
🟢 Гайды по нейросетям без воды
🟢 Полезные инструменты и лайфхаки
🟢 Честные мысли о трендах в IT
Живые обсуждения для тех, кто не просто читает про ИИ, а строит его будущее🚀.
👉 Подписаться: https://t.me/+mAs8eSHFkjBkYTUy
2 519
👩💻 Фишка: Async Iterator и for-await-of — итерация по асинхронным данным
Асинхронные итераторы и цикл for-await-of позволяют последовательно обрабатывать асинхронные данные, такие как стримы, пагинированные API или события. Идеально для работы с постепенно поступающими данными.
// Асинхронный генератор для пагинированных данных
async function* fetchPaginatedData(url, limit = 3) {
let page = 1;
while (page <= limit) {
// Имитация асинхронного запроса
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items; // Возвращаем данные текущей страницы
page++;
// Останавливаемся если данных больше нет
if (!data.hasNextPage) break;
}
}
// Использование с for-await-of
(async () => {
console.log('Начинаем загрузку данных...');
try {
for await (const items of fetchPaginatedData('/api/users')) {
console.log(`Получено ${items.length} записей`);
items.forEach(user => console.log(`- ${user.name}`));
}
} catch (error) {
console.error('Ошибка загрузки:', error);
}
console.log('Загрузка завершена');
})();
// Асинхронный итератор для стрима
async function* createAsyncStream(source, chunkSize = 10) {
let buffer = [];
for (const item of source) {
buffer.push(item);
if (buffer.length >= chunkSize) {
yield buffer; // Возвращаем накопленные данные
buffer = []; // Очищаем буфер
}
}
// Возвращаем остаток
if (buffer.length > 0) {
yield buffer;
}
}
// Пример с сенсорными данными
async function processSensorData() {
const sensor = {
async *Symbol.asyncIterator {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
yield { temperature: 20 + Math.random() * 5, timestamp: Date.now() };
}
}
};
const readings = [];
for await (const data of sensor) {
console.log(`Температура: ${data.temperature.toFixed(1)}°C`);
readings.push(data);
}
console.log(`Всего получено: ${readings.length} показаний`);
}
// Обработка нескольких асинхронных источников
async function* mergeAsyncStreams(...streams) {
const promises = streams.map(async function* (stream) {
for await (const item of stream) {
yield item;
}
});
for (const promise of promises) {
for await (const item of promise) {
yield item;
}
}
}
// Практический пример: логирование в реальном времени
async function monitorLogs() {
const logStream = {
async *Symbol.asyncIterator {
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield [${new Date().toISOString()}] System heartbeat;
}
}
};
let count = 0;
for await (const log of logStream) {
console.log(log);
count++;
if (count >= 5) break; // Останавливаемся после 5 логов
}
}
📌 Преимущества:
— Работа с постепенно поступающими данными
— Обработка бесконечных стримов
— Эффективное потребление памяти
— Чистая обработка асинхронных последовательностей
— Интеграция с современными API (fetch streams, WebSockets)
🧡 Ghostly Frontend | #фишки2 519
Сеньор за полгода? 📈
Эта девушка получила оффер в IT-компанию, хотя весь её опыт — пара курсов с ютуба 😱
Она воспользовалась ИИ-помощником и легко скрыла все свои пробелы в знаниях.
Теперь впереди: ⤵️
удалёнка, стартовое обучение и ставка 55$ в час.
Проходи собеседования вместе с Interview Ninja 🥷
Проверь успех на себе — есть 100 бесплатных запросов на день.
👉 @interview_ninja
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
