Привет! Хотя регулярность постов и изменилась, неделя по-прежнему начинается с разбора задач с собеседований)
У нас есть пример типизированого массива:
И дана функция поиска по массиву, которую нужно типизировать:
Пример использования этой функции с массивом:
Задача:
Типизировать функцию
Решение:
1.
-
-
2. Параметры функции:
-
-
-
Вот и все) Мы типизировали функцию с помощью дженериков и сделали её универсальной для любых подобных массивов. Теперь мы можем гарантировать, что функция будет принимать только валидные аргументы.
В этом примере я не указал явно тип возвращаемого значения, так как TypeScript подтянет его автоматически. Но если функция будет меняться, лучше явно прописывать возвращаемый тип, чтобы избежать случайных ошибок в будущем.
#JavaScript #interview #typescript
У нас есть пример типизированого массива:
interface Student {
name: string;
age: number;
hasScar: boolean;
}
const students: Student[] = [
{ name: "Harry", age: 17, hasScar: true },
{ name: "Ron", age: 17, hasScar: false },
{ name: "Hermione", age: 16, hasScar: false },
];
И дана функция поиска по массиву, которую нужно типизировать:
function getBy(arr, prop, value) {
return arr.find(item => item[prop] === value) || null;
}
Пример использования этой функции с массивом:
const result = getBy(students, "name", "Hermione");
console.log(result);
Задача:
Типизировать функцию
getBy. Решение:
function getBy<T, K extends keyof T>(arr: T[], prop: K, value: T[K]) {
return arr.find(item => item[prop] === value) || null;
}
1.
<T, K extends keyof T> — дженерик:-
T — это универсальный тип, который представляет тип объекта внутри массива arr.-
K extends keyof T — означает, что K — это ключ объекта T. Тип keyof T — это объединение всех ключей объекта T. 2. Параметры функции:
-
arr: T[] — массив объектов типа T.-
prop: K — ключ, по которому будем искать совпадение.-
value: T[K] — значение, с которым будем сравнивать. Тип значения автоматически подтягивается из типа свойства prop. Вот и все) Мы типизировали функцию с помощью дженериков и сделали её универсальной для любых подобных массивов. Теперь мы можем гарантировать, что функция будет принимать только валидные аргументы.
В этом примере я не указал явно тип возвращаемого значения, так как TypeScript подтянет его автоматически. Но если функция будет меняться, лучше явно прописывать возвращаемый тип, чтобы избежать случайных ошибок в будущем.
#JavaScript #interview #typescript
👍8🔥4🤯4
Сегодня у нас очередная задача с собеседования, связанная с рекурсией)
Задача:
Нужно распарсить вложенный объект, чтобы получить значение самого глубокого уровня.
Пример вызова функции:
Решение:
1.
2. Берём первое значение —
3. Если это значение — не объект, возвращаем его.
4. Если это объект, вызываем
Для кого-то эта задача может показаться совсем простой, но на практике некоторые мидлы задумываются над ней.
#interview #JavaScript
Задача:
Нужно распарсить вложенный объект, чтобы получить значение самого глубокого уровня.
Пример вызова функции:
const obj = { red: { fast: { fancy: { car: "lamba" } } } };
console.log(objParse(obj)); // "lamba"
Решение:
function objParse(obj) {
const [value] = Object.values(obj); // 1, 2
if (typeof value !== "object") { // 3
return value;
}
return objParse(value); // 4
}
1.
Object.values(obj) возвращает массив всех значений объекта. 2. Берём первое значение —
value.3. Если это значение — не объект, возвращаем его.
4. Если это объект, вызываем
objParse рекурсивно.Для кого-то эта задача может показаться совсем простой, но на практике некоторые мидлы задумываются над ней.
#interview #JavaScript
🔥9👍4
Недавно мы разбирали(тык) работу с ошибками в JS.
Сегодня рассмотрим создание кастомных ошибок. Они позволяют точнее описывать, что именно пошло не так, и обрабатывать ошибки более точечно.
Вместо обычного
Что происходит?
1. Мы наследуемся от встроенного
2. Функция
3. В блоке
4. В блоке
Зачем это нужно?
1. Проще различать типы ошибок
2. Можно точечно обрабатывать нужные ошибки с помощью
3. Можно сделать более детальные ошибки.
#JavaScript #BestPractices
Сегодня рассмотрим создание кастомных ошибок. Они позволяют точнее описывать, что именно пошло не так, и обрабатывать ошибки более точечно.
Вместо обычного
Error можно создать свой класс, унаследованный от него:
class ValidationError extends Error { // 1
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user) { // 2
if (!user.name) {
throw new ValidationError("Поле 'name' обязательно");
}
}
try {
validateUser({}); // 3
} catch (error) {
if (error instanceof ValidationError) { // 4
console.error("Ошибка валидации:", error.message);
} else {
throw error;
}
}
Что происходит?
1. Мы наследуемся от встроенного
Error, чтобы определить новый тип ошибки — ValidationError.2. Функция
validateUser проверяет, есть ли у объекта поле name. Если его нет — выбрасывает кастомную ошибку ValidationError с понятным сообщением.3. В блоке
try вызываем validateUser, передавая объект без name.4. В блоке
catch ловим ошибку и проверяем, является ли она экземпляром ValidationError с помощью instanceof.Зачем это нужно?
1. Проще различать типы ошибок
2. Можно точечно обрабатывать нужные ошибки с помощью
instanceof.3. Можно сделать более детальные ошибки.
#JavaScript #BestPractices
👍11🔥3👏1
Привет! Начнем эту 3-дневную рабочую неделю с небольшой алгоритмической задачи.
Задача
Напишите функцию, которая возвращает массив уникальных значений, отсортированных:
1. по количеству повторений (по убыванию),
2. если количество совпадает — по алфавиту.
Пример входных данных:
Ожидаемый результат:
Решение:
Разбор:
1. Сначала считаем количество повторений через объект
2. Сортируем ключи — по убыванию количества, а при равенстве — по алфавиту.
Часто в подобных задачах просят не только написать решение, но и посчитать сложность алгоритма.
Предлагаю вам самим попробовать оценить её и, если есть желание, поделиться в комментариях или в нашем чате)
#interview #JavaScript
Задача
Напишите функцию, которая возвращает массив уникальных значений, отсортированных:
1. по количеству повторений (по убыванию),
2. если количество совпадает — по алфавиту.
Пример входных данных:
const words = [
'banana', 'grapefruit', 'banana', 'grapefruit',
'banana', 'orange', 'banana', 'grapefruit',
'grapefruit', 'grapefruit'
];
Ожидаемый результат:
['grapefruit', 'banana', 'orange']
Решение:
function getSortedUnique(words) {
const freq = {};
for (let word of words) {
freq[word] = (freq[word] || 0) + 1;
}
return Object.keys(freq).sort((a, b) => {
if (freq[b] !== freq[a]) {
return freq[b] - freq[a]; // по убыванию частоты
}
return a.localeCompare(b); // по алфавиту
});
}
Разбор:
1. Сначала считаем количество повторений через объект
freq.2. Сортируем ключи — по убыванию количества, а при равенстве — по алфавиту.
Часто в подобных задачах просят не только написать решение, но и посчитать сложность алгоритма.
Предлагаю вам самим попробовать оценить её и, если есть желание, поделиться в комментариях или в нашем чате)
#interview #JavaScript
👍10🔥4🤯1
На фронтенде производительность — ключевой фактор для хорошего пользовательского опыта. Чем быстрее загружается и работает приложение, тем лучше.
Одним из инструментов оптимизации является динамический импорт. Он позволяет загружать модули только тогда, когда они действительно нужны.
Как это?
Вместо статического импорта:
Используем динамический:
Здесь модуль
Зачем?
- Ускоряет загрузку.
- Уменьшает размер основного бандла.
Импорт в React
В React для динамической загрузки компонентов есть
Здесь
Когда стоит использовать?
Например, если в приложении есть библиотека для работы с PDF, которую что-то делает по клику на кнопку, то нет смысла грузить её сразу. Такие библиотеки обычно тяжёлые, и динамическая загрузка позволяет сэкономить время и ресурсы.
Не забывайте, что никто не любит ждать. Если ваш сайт долго загружается, пользователь просто уйдёт к конкурентам
#BestPractices #react #JavaScript
Одним из инструментов оптимизации является динамический импорт. Он позволяет загружать модули только тогда, когда они действительно нужны.
Как это?
Вместо статического импорта:
import { heavyFunc } from './heavyModule.js';
button.addEventListener('click', () => {
heavyFunc();
});
Используем динамический:
button.addEventListener('click', async () => {
const module = await import('./heavyModule.js');
module.heavyFunc();
});
Здесь модуль
heavyModule.js загрузится только после клика по кнопке, а не сразу при загрузке страницы. Зачем?
- Ускоряет загрузку.
- Уменьшает размер основного бандла.
Импорт в React
В React для динамической загрузки компонентов есть
React.lazy.
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<LazyComponent />
</Suspense>
);
}
Здесь
MyComponent загрузится только при первом рендере LazyComponent.Когда стоит использовать?
Например, если в приложении есть библиотека для работы с PDF, которую что-то делает по клику на кнопку, то нет смысла грузить её сразу. Такие библиотеки обычно тяжёлые, и динамическая загрузка позволяет сэкономить время и ресурсы.
Не забывайте, что никто не любит ждать. Если ваш сайт долго загружается, пользователь просто уйдёт к конкурентам
#BestPractices #react #JavaScript
🔥7❤3👍3
Многие из нас сталкиваются с проблемой, когда нужно сделать сразу несколько запросов. Часто такие запросы выполняются по цепочке, хотя на самом деле это не всегда нужно. Если запросы не зависят от ответа предыдущего, ждать их друг за другом бессмысленно. В таких случаях запросы лучше выполнять параллельно, чтобы ускорить работу и улучшить пользовательский опыт.
Пример работающего, но проблемного кода:
Здесь каждый запрос дожидается ответа от предыдущего. Время выполнения функции — сумма времени всех запросов.
Как можно улучшить?
Проблема решается с помощью
Более правильная реализация:
Теперь все запросы отправляются одновременно, и время выполнения функции — это время самого долгого запроса. Такой подход значительно ускоряет загрузку.
Проблема
Если хотя бы один из запросов упадёт, весь промис сразу же перейдёт в ошибку, и мы не получим результаты остальных запросов. Для решения этой проблемы есть
Помните, что при разработке важно всегда думать о пользователе и его удобстве. Ускорение загрузки и отзывчивость интерфейса делают продукт лучше 🙌
#JavaScript #BestPractices
Пример работающего, но проблемного кода:
async function getData() {
try {
const user = await fetch('/user');
const posts = await fetch('/posts');
const comments = await fetch('/comments');
console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}
Здесь каждый запрос дожидается ответа от предыдущего. Время выполнения функции — сумма времени всех запросов.
Как можно улучшить?
Проблема решается с помощью
Promise.all. Это функция, которая принимает массив промисов и позволяет запустить их одновременно. Она возвращает новый промис, который завершится успешно, когда все переданные промисы завершатся, или упадёт, если хотя бы один промис вернёт ошибку.Более правильная реализация:
async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');
const [user, posts, comments] = await Promise.all([
userPromise,
postsPromise,
commentsPromise
]);
console.log('Все данные получены');
} catch (error) {
console.error('Ошибка:', error);
}
}
Теперь все запросы отправляются одновременно, и время выполнения функции — это время самого долгого запроса. Такой подход значительно ускоряет загрузку.
Проблема
Если хотя бы один из запросов упадёт, весь промис сразу же перейдёт в ошибку, и мы не получим результаты остальных запросов. Для решения этой проблемы есть
Promise.allSettled, который позволяет получить результаты всех промисов, даже если некоторые из них упали. Но эту тему мы затронем в одном из следующих постов.Помните, что при разработке важно всегда думать о пользователе и его удобстве. Ускорение загрузки и отзывчивость интерфейса делают продукт лучше 🙌
#JavaScript #BestPractices
👍11🔥4
В одном из предыдущих постов мы кратко познакомились с SOLID. Сегодня начнем серию постов и разберём первую букву — S. Эта буква расшифровывается как Single Responsibility Principle или принцип единственной ответственности.
Этот принцип говорит, что каждый модуль или класс должен отвечать только за одну задачу.
Пример плохого подхода:
Класс
Как улучшить?
Разделять и властвовать
Мы разделяем обязанности и теперь каждый класс отвечает за свою конкретную задачу. Такой код будет в разы проще тестровать и поддерживать в будущем)
Надеюсь, что этот пост понравился и можно продолжать. Ставь лайк😌
#BestPractices #JavaScript
Этот принцип говорит, что каждый модуль или класс должен отвечать только за одну задачу.
Пример плохого подхода:
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
save() {
// сохраняет пользователя в базу
}
sendEmail(message) {
// отправляет письмо пользователю
}
}
Класс
User хранит данные, отвечает за сохранение и за отправку почты. Такой код сложнее расширять, тестировать и в нём выше риск случайно что-то сломать при изменениях.Как улучшить?
Разделять и властвовать
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
// логика сохранения в базу
}
}
class EmailService {
sendEmail(user, message) {
// логика отправки письма
}
}
Мы разделяем обязанности и теперь каждый класс отвечает за свою конкретную задачу. Такой код будет в разы проще тестровать и поддерживать в будущем)
Надеюсь, что этот пост понравился и можно продолжать. Ставь лайк
#BestPractices #JavaScript
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥4
В прошлом посте мы разобрали
Что это за метод?
Метод
После этого он возвращает массив объектов, каждый из которых содержит статус выполнения соответствующего промиса и его результат или причину ошибки.
Пример:
Теперь даже если один из запросов упал, остальные продолжат работать.
Результат выполнения
Если промис выполнился:
Если промис отклонился:
Выбор метода всегда остаётся за вами и зависит от конкретной задачи) В будущем разберем другие методы и некоторые задачи по промисам.
#JavaScript
Promise.all, а сегодня разберём Promise.allSettled. В конце прошлого поста затронули проблему, что если один из промисов падает, то Promise.all сразу выдаёт ошибку и игнорирует остальные.Promise.allSettled работает иначе. Он ждёт, пока завершатся все промисы, и возвращает массив результатов по каждому из них. Неважно, завершился он успешно или с ошибкой.Что это за метод?
Метод
Promise.allSettled возвращает промис, который ожидает завершения всех переданных промисов, вне зависимости от того, успешно они выполнились или нет.После этого он возвращает массив объектов, каждый из которых содержит статус выполнения соответствующего промиса и его результат или причину ошибки.
Пример:
async function getData() {
try {
const userPromise = fetch('/user');
const postsPromise = fetch('/posts');
const commentsPromise = fetch('/comments');
const results = await Promise.allSettled([
userPromise,
postsPromise,
commentsPromise
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${index + 1} выполнен`, result.value);
} else {
console.warn(`Запрос ${index + 1} упал`, result.reason);
}
});
} catch (error) {
console.error('Ошибка:', error);
}
}
Теперь даже если один из запросов упал, остальные продолжат работать.
Результат выполнения
Если промис выполнился:
{
status: "fulfilled",
value: ... // значение, которое вернул промис
}
Если промис отклонился:
{
status: "rejected",
reason: ... // ошибка
}
Выбор метода всегда остаётся за вами и зависит от конкретной задачи) В будущем разберем другие методы и некоторые задачи по промисам.
#JavaScript
🔥9👍4
В прошлый раз мы познакомились с методом
Задача:
Напишите функцию
Пример использования:
Решение:
Что происходит:
1. Каждый промис оборачивается так, чтобы вернуть объект с результатом и статусом.
2. Обычные значения тоже оборачиваются в промис через Promise.resolve, чтобы с ними можно было работать как с промисами.
3.
Это обычная задача на промисы, которая проверяет понимание работы с ними и их знание.
#interview #JavaScript
Promise.allSettled, а сегодня разберём задачу с собеседования с реализацией кастомного allSettled.Задача:
Напишите функцию
allSettled, которая работает аналогично встроенному Promise.allSettled.Пример использования:
const p1 = Promise.resolve(1);
const p2 = Promise.reject('Ошибка');
const p3 = new Promise(res => setTimeout(() => res(42), 100));
allSettled([p1, p2, p3]).then(results => {
console.log(results);
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Ошибка' },
{ status: 'fulfilled', value: 42 }
]
*/
});
Решение:
function allSettled(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
}
Что происходит:
1. Каждый промис оборачивается так, чтобы вернуть объект с результатом и статусом.
2. Обычные значения тоже оборачиваются в промис через Promise.resolve, чтобы с ними можно было работать как с промисами.
3.
Promise.all ждёт, пока все обёрнутые промисы завершатся, и возвращает массив результатов.Это обычная задача на промисы, которая проверяет понимание работы с ними и их знание.
#interview #JavaScript
🔥9👍4
Продолжаем серию постов про SOLID.
Сегодня разберём вторую букву — O, которая расшифровывается как Open/Closed Principle или принцип открытости/закрытости.
О чем этот принцип?
Этот принцип о том, что сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это значит, что вы можете добавлять новую функциональность, не изменяя существующий код.
Пример
Если нужно добавить новую фигуру, придётся менять метод
Как улучшить?
Используем наследование, чтобы сделать класс открытым для расширения:
Теперь, чтобы добавить новую фигуру, мы просто создаём новый класс, не трогая существующий код.
Какие плюсы это даёт?
— Новый функционал добавляется без риска сломать старый код.
— Меньше багов, так как основной код остаётся нетронутым.
— Легче тестировать отдельные классы с конкретной логикой.
#BestPractices #JavaScript
Сегодня разберём вторую букву — O, которая расшифровывается как Open/Closed Principle или принцип открытости/закрытости.
О чем этот принцип?
Этот принцип о том, что сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это значит, что вы можете добавлять новую функциональность, не изменяя существующий код.
Пример
class Shape {
constructor(type) {
this.type = type;
}
getArea() {
if (this.type === 'circle') {
// Логика для круга
return Math.PI * 5 * 5;
} else if (this.type === 'rectangle') {
// Логика для прямоугольника
return 10 * 5;
}
}
Если нужно добавить новую фигуру, придётся менять метод
getArea, добавляя новую ветку в if. Это нарушает принцип закрытости, так как мы модифицируем существующий код.Как улучшить?
Используем наследование, чтобы сделать класс открытым для расширения:
class Shape {
getArea() {
throw new Error('Method getArea() must be implemented');
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
// Теперь можно легко добавить новую фигуру
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}
getArea() {
return 0.5 * this.base * this.height;
}
}
// Использование
const shapes = [new Circle(5), new Rectangle(10, 5), new Triangle(6, 8)];
shapes.forEach(shape => console.log(shape.getArea()));
Теперь, чтобы добавить новую фигуру, мы просто создаём новый класс, не трогая существующий код.
Какие плюсы это даёт?
— Новый функционал добавляется без риска сломать старый код.
— Меньше багов, так как основной код остаётся нетронутым.
— Легче тестировать отдельные классы с конкретной логикой.
#BestPractices #JavaScript
👍9👏6