fa
Feedback
Ghostly Frontend

Ghostly Frontend

رفتن به کانال در Telegram

Погружаемся в мир Frontend: задачи, фишки, библиотеки и террабайты полезного материала. Сотрудничество: @heywan_n1 Цены: @heywan_media

نمایش بیشتر
2 518
مشترکین
-124 ساعت
-97 روز
-3130 روز
آرشیو پست ها
👩‍💻 Фишка: 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 | #фишки

Tab Bar Interaction — система вкладок с анимацией Доступен выбор светлой и темной темы, написана на JS, HTML и CSS. ➡️ Ссылка на код 🧡 Ghostly Frontend | #codepen

Хорошо, что наш мир создали не программисты 🧡 Ghostly Frontend | #мем
Хорошо, что наш мир создали не программисты 🧡 Ghostly Frontend | #мем

👩‍💻 Выбираем стек для веб-сайта: SSR, SSG, CSR и другие В современном мире веб-разработки выбор стратегии рендеринга сайта
👩‍💻 Выбираем стек для веб-сайта: SSR, SSG, CSR и другие В современном мире веб-разработки выбор стратегии рендеринга сайта является одним из ключевых решений, определяющих его производительность, оптимизацию для поисковых систем (SEO) и пользовательский опыт. В статье рассматриваются основные типы рендеринга: их преимущества, недостатки, влияние на SEO, производительность и многое другое. ➡️ Ссылка на статью 🧡 Ghostly Frontend | #статьи

👩‍💻 Фишка: 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 | #фишки

Анимированные 3D карточки Карточки с анимацией при наведении и возможностью изменения цвета ➡️ Ссылка на код 🧡 Ghostly Frontend | #codepen

🧡 Ghostly Frontend | #мем
🧡 Ghostly Frontend | #мем

👩‍💻 Новые реактивные формы в Angular: Signal Forms API Статья от фронтенд-разраба из Т-Банка, который расскажет, как с помо
👩‍💻 Новые реактивные формы в Angular: Signal Forms API Статья от фронтенд-разраба из Т-Банка, который расскажет, как с помощью сигналов теперь можно быстро создать типобезопасную форму, настроить валидацию с условными правилами, гибко управлять состояниями полей и легко встраивать кастомные компоненты-контролы. ➡️ Читать статью 🧡 Ghostly Frontend | #статьи

👩‍💻 Фишка: 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 | #фишки

Как разработчик решил параллельно найму пилить свои бизнес-проекты с нулевым опытом: дневник с передовой Меня зовут Александр Торбек, И я попал в день сурка: код писать умею, зарплата стабильная. Но в заднице зудит ощущение катастрофического застоя. Поэтому я сделал глупейшую вещь — начал разрабатывать продукты. Без связей, плана и стратегии. В блоге буду фиксировать: — идеи (и почему 90% из них — говно собаки) — что сделал, сколько заработал — мысли айтишника, который впервые думает как продакт, а не как тупой исполнитель Я хочу пройти весь путь от основателя продукта до продажника. И выяснить, смогу ли без бизнес-бэкграунда выйти на уровень дядек в элитных пиджаках. Если тоже хотите создавать свои продукты — посмотрите, как я набиваю шишки первым: @atorbek_it

Анимированная панель навигации Простая и минималистичная панель, сделана на SCSS, TypeScript и SVG. ➡️ Ссылка на код 🧡 Ghostly Frontend | #codepen

Если бы CSS работал в реальности: 🧡 Ghostly Frontend | #мем
Если бы CSS работал в реальности: 🧡 Ghostly Frontend | #мем

👩‍💻 Cards with inverted border-radius — Карточки товаров с интересным решением для кнопки. Сделан на: SCSS ➡️ Код тут 🧡 Ghostly Frontend | #codepen

👩‍💻 Фишка: 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 | #фишки

⚡️ Писать код руками больше не нужно! Привет. Меня зовут Кирилл. Я создаю топовые подборки с уроками по AI и вайбкодингу: — 6
⚡️ Писать код руками больше не нужно! Привет. Меня зовут Кирилл. Я создаю топовые подборки с уроками по AI и вайбкодингу: — 60+ промптов для дебага: поиск ошибок, оптимизация кода — 40 бесплатных курсов по вайбкодингу — 150 гайдов, как использовать ChatGPT, Claude Code, Antigravity, Cursor, Perplexity, Lovable — 100 готовых модулей: авторизация, админки, логирование, тесты, i18n — 37 MCP серверов: дизайн, разработка, Тесты/QA, деплой Всего 10 минут в день на канале и ты начнешь писать код на 85% быстрее и сможешь за пару дней закрывать спринты. Подписывайся, чтобы получать подборки каждую неделю!

🖥 JSFiddle — онлайн-редактор для тестирования и обмена frontend-кодом JSFiddle — это простая и быстрая песочница для написан
🖥 JSFiddle — онлайн-редактор для тестирования и обмена frontend-кодом JSFiddle — это простая и быстрая песочница для написания HTML, CSS и JavaScript прямо в браузере. Подходит для отладки, демонстрации решений, экспериментов и обмена кодом с другими разработчиками.
Вы можете подключать сторонние библиотеки (jQuery, Vue, React и др.), сохранять свои «фиддлы» и делиться ссылками. Отлично подойдёт как для новичков, так и для менторов и тех, кто участвует в код-ревью.
⛓️ Ссылка на ресурс 🧡 Ghostly Frontend | #ресурсы

В IT хватает тех, кто пересказывает чужие статьи. Но есть те, кто пишет изнутри системы. NeuroNinja 🥷🏻 - авторский канал инженера СберТехнологий, который показывает, что происходит по ту сторону интерфейса. В канале вы найдете: 🟢 Разборы реальных кейсов из практики 🟢 Гайды по нейросетям без воды 🟢 Полезные инструменты и лайфхаки 🟢 Честные мысли о трендах в IT Живые обсуждения для тех, кто не просто читает про ИИ, а строит его будущее🚀. 👉 Подписаться: https://t.me/+mAs8eSHFkjBkYTUy

👩‍💻 Фишка: 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 | #фишки

🧡 Ghostly Frontend | #мемы
🧡 Ghostly Frontend | #мемы

Сеньор за полгода? 📈 Эта девушка получила оффер в IT-компанию, хотя весь её опыт — пара курсов с ютуба 😱 Она воспользовалас
Сеньор за полгода? 📈 Эта девушка получила оффер в IT-компанию, хотя весь её опыт — пара курсов с ютуба 😱 Она воспользовалась ИИ-помощником и легко скрыла все свои пробелы в знаниях. Теперь впереди: ⤵️ удалёнка, стартовое обучение и ставка 55$ в час. Проходи собеседования вместе с Interview Ninja 🥷 Проверь успех на себе — есть 100 бесплатных запросов на день. 👉 @interview_ninja