РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ / Middle FRONTEND разработчик ЧистоАПП -
Вопрос 1. Расскажите о своём профессиональном опыте и образовании.
Таймкод: 00:01:27
Ответ собеседника: Правильный. Студент 2 курса РТУ МИРЭА, начал карьеру с вёрстки в 16 лет, затем фриланс (HTML/CSS/JS), освоил React, работал в компании Lucky Laky фронтенд-разработчиком.
Правильный ответ: При презентации профессионального опыта на позицию разработчика рекомендуется структурировать ответ по ключевым блокам:
-
Текущая роль и фокус
Пример: «Специализируюсь на backend-разработке на Go с акцентом на высоконагруженные системы. В последние 2 года активно работаю с распределёнными системами и микросервисной архитектурой». -
Образование и ранний опыт
Упомяните значимые курсы или проекты. Для студента:
«Изучаю Computer Science в РТУ МИРЭА, параллельно развиваюсь в промышленной разработке. Первый коммерческий опыт получил в 16 лет, создавая адаптивную вёрстку для малого бизнеса». -
Ключевые этапы карьеры
Детализируйте технологии и достижения:
«На фрилансе реализовал 15+ SPA-приложений на React/TypeScript. В Lucky Laky участвовал в рефакторинге legacy-кода, что сократило время загрузки интерфейса на 40%. Переход на Go начал с разработки CLI-утилиты для внутреннего тестирования API». -
Технический стек
Сгруппируйте навыки по уровням:- **Языки**: Go (2 года), TypeScript (3 года)
- **Базы данных**: PostgreSQL (оптимизация запросов, EXPLAIN ANALYZE), Redis
- **Инфраструктура**: Docker, Kubernetes, GitLab CI/CD
- **Паттерны**: Clean Architecture, CQRS, Event Sourcing -
Пет-проекты (для junior/middle)
Пример с техническими деталями:
«Разработал асинхронный воркер на Go для обработки очередей RabbitMQ. Реализовал пул воркеров с динамическим scaling на основе метрик Prometheus:func (p *WorkerPool) AdjustWorkers(queueLength int) {
targetWorkers := queueLength / p.itemsPerWorker
if targetWorkers > p.maxWorkers {
targetWorkers = p.maxWorkers
}
p.resize(targetWorkers)
}Система обрабатывает 5K+ сообщений/сек с задержкой <10ms».
-
Карьерные цели
«Сейчас стремлюсь углубиться в проектирование высокодоступных систем. Интересуюсь решениями вроде распределённых транзакций через Saga Pattern и оптимизацией запросов в PostgreSQL с использованием частичных индексов:CREATE INDEX idx_orders_active ON orders (user_id)
WHERE status = 'active';
Рекомендация: Для backend-ролей делайте акцент на архитектурных решениях и сложных технических задачах, а не только на стеке технологий. Упоминайте метрики результатов («сократил latency на 30%», «повысил test coverage до 85%»).
Вопрос 2. Опишите один из реализованных вами проектов.
Таймкод: 00:02:51
Ответ собеседника: Правильный. Разработал приложение Sporty Time для бронирования спортивных площадок с функционалом регистрации, выбора города и приглашения друзей.
Правильный ответ: При описании проекта для позиции backend-разработчика на Go рекомендуется структурировать ответ по ключевым аспектам:
1. Архитектурные решения
- Микросервисная структура: API Gateway (Go + Gin), Booking Service (Go + gRPC), Notification Service (Python для email/SMS).
- Схема взаимодействия:
graph LR
A[Client] --> B[API Gateway]
B --> C[Auth Service]
B --> D[Booking Service]
D --> E[(PostgreSQL)]
D --> F[RabbitMQ]
F --> G[Notification Service]
2. Реализация ключевого функционала на Go
Пример обработки бронирования с конкурентным контролем:
func (s *BookingService) CreateBooking(ctx context.Context, req *pb.BookingRequest) (*pb.BookingResponse, error) {
// Использование транзакции с уровнем изоляции Repeatable Read
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
if err != nil {
return nil, status.Errorf(codes.Internal, "transaction failed: %v", err)
}
defer tx.Rollback()
// Проверка доступности слота через SELECT FOR UPDATE
var available bool
err = tx.QueryRowContext(ctx,
"SELECT available FROM time_slots WHERE id = $1 FOR UPDATE",
req.SlotId,
).Scan(&available)
if !available {
return nil, status.Error(codes.FailedPrecondition, "slot already booked")
}
// Резервирование с обработкой таймаута контекста
_, err = tx.ExecContext(ctx,
"INSERT INTO bookings (user_id, slot_id, status) VALUES ($1, $2, 'confirmed')",
req.UserId, req.SlotId,
)
if err != nil {
return nil, handleDBError(err)
}
if err := tx.Commit(); err != nil {
return nil, status.Errorf(codes.Internal, "commit failed: %v", err)
}
// Публикация события в очередь
msg := amqp.Publishing{Body: marshalBookingEvent(req)}
if err := s.rabbitCh.PublishWithContext(ctx, "bookings", "", false, false, msg); err != nil {
log.Printf("Failed to publish event: %v", err)
}
return &pb.BookingResponse{BookingId: uuid.New().String()}, nil
}
3. Оптимизация запросов к БД
- Использование частичных индексов для часто запрашиваемых данных:
CREATE INDEX idx_active_bookings ON bookings (user_id, slot_id)
WHERE status IN ('confirmed', 'pending'); - Реализация кэширования ближайших доступных слотов в Redis с TTL 30 секунд:
func (c *Cache) GetSlots(ctx context.Context, venueID string) ([]TimeSlot, error) {
key := fmt.Sprintf("slots:%s", venueID)
if slots, err := c.client.Get(ctx, key).Result(); err == nil {
return unmarshalSlots(slots), nil
}
// Кэш-мисс: запрос к БД и обновление кэша
}
4. Метрики и результаты
- Обработка 120 RPS на инстансе c4.large (2 vCPU, 4GB RAM)
- Снижение 99-го перцентиля задержки с 450ms до 85ms после оптимизации запросов N+1
- Реализация idempotency keys для предотвращения дублирующих бронирований
5. Интеграция с внешними сервисами
- Асинхронная отправка уведомлений через RabbitMQ с retry-логикой:
func (n *Notifier) PublishNotification(msg amqp.Delivery) {
retries := 0
for {
err := n.processNotification(msg.Body)
if err == nil {
msg.Ack(false)
return
}
if retries >= 3 {
msg.Nack(false, false)
return
}
retries++
time.Sleep(time.Duration(math.Pow(2, float64(retries))) * time.Second)
}
}
Рекомендация: Всегда связывайте технические решения с бизнес-результатами («сократили количество ошибочных бронирований на 15% за счёт транзакционных блокировок», «увеличили конверсию на 20% через кэширование популярных запросов»).
Вопрос 3. Опишите организацию команды в вашем последнем проекте.
Таймкод: 00:03:12
Ответ собеседника: Правильный. В команде было: 1 фронтенд-разработчик (кандидат), 1 тимлид (помогал с фронтендом), 1 бэкенд-разработчик, 1 тестировщик.
Правильный ответ: Для эффективной работы над проектом была реализована гибридная модель управления с элементами Scrum и Kanban. Вот детализация процессов:
- Роли и зоны ответственности
-
Тимлид:
- Проводил ежедневные stand-up митинги с фокусом на блокерах
- Реализовывал архитектурные решения (например, выбор между gRPC и REST API)
- Вёл code review критических компонентов:
// Пример проверки конкурентного кода
func (s *Service) ProcessOrder(ctx context.Context) error {
s.mu.Lock() // Проверяли использование sync.RWMutex вместо Mutex для read-heavy кейсов
defer s.mu.Unlock()
...
}
-
Бэкенд-разработчик (я):
- Разработка ядра системы на Go (95% кодовой базы)
- Оптимизация запросов к PostgreSQL (EXPLAIN ANALYZE, индексная оптимизация)
- Настройка CI/CD через GitLab pipelines:
# .gitlab-ci.yml
test:
stage: test
image: golang:1.21
script:
- go test -race -coverprofile=coverage.out ./...
- go tool cover -func=coverage.out
-
Фронтенд-разработчик:
- Верстка компонентов React с TypeScript
- Интеграция с бэкендом через auto-generated Swagger клиент
-
Тестировщик:
- Реализация нагрузочных тестов на Gatling (до 1000 RPS)
- Автоматизация E2E-тестов через Playwright
- Процессы разработки
- Гит-стратегия: GitFlow с защитой веток
main/release - Code Review: Обязательные 2 апрува перед мержем, проверки:
- SQL-инъекции (особенно в динамических запросах):
// Плохо: q := fmt.Sprintf("SELECT * FROM users WHERE id = %s", input)
// Хорошо:
rows, err := db.Query("SELECT * FROM users WHERE id = $1", input) - Утечки горутин (проверка через pprof)
- SQL-инъекции (особенно в динамических запросах):
- Деплой: Blue-Green деплойменты в Kubernetes с feature flags
- Метрики эффективности
- Lead Time: Сократили с 5 дней до 8 часов через:
- Параллелизацию тестов в CI
- Автоматизацию миграций БД (Goose)
- Инциденты: Менее 2% отказов деплоев благодаря:
- Canary-релизам
- Integration-тестам в продакшн-подобном окружении (testcontainers)
- Инструментарий
graph TD
A[Jira] --> B[GitLab]
B --> C[CI Pipeline]
C --> D[Registry]
D --> E[Kubernetes]
E --> F[Prometheus/Grafana]
- Коммуникация
- Ежедневные стендапы с фокусом на проблемах, а не статусах
- Ретроспективы раз в 2 недели с анализом метрик:
-- Анализ скорости закрытия задач
SELECT
AVG(EXTRACT(EPOCH FROM (closed_at - created_at))) / 3600 AS avg_hours
FROM issues
WHERE sprint_id = 45;
Рекомендация: Всегда связывайте структуру команды с техническими результатами ("благодаря pair programming между тимлидом и фронтендером сократили количество багов в UI на 40%", "за счёт разделения зон ответственности в БД достигли 99.95% доступности").
Вопрос 4. Как был организован процесс работы над задачами?
Таймкод: 00:03:28
Ответ собеседника: Неполный. Двухнедельные спринты с последующей проверкой тимлидом, доработки при необходимости. Не упомянуты процессы аналитики и проектирования.
Правильный ответ: Полный цикл разработки включал следующие этапы:
- Подготовка и проектирование
- Глубинный анализ требований:
Проводились сессии Event Storming для выявления bounded context. Например, для модуля бронирования:Команда: CreateBooking
Событие: BookingCreated
Политика: Не более 3 активных броней на пользователя - Техническое проектирование:
- Для сложных фич создавались ADR (Architecture Decision Records):
## Выбор протокола коммуникации между сервисами
Решение: gRPC вместо REST
Причины:
- Типизация через protobuf
- Поддержка потоковой передачи данных
- Встроенная retry-логика - Схема БД версионировалась через миграции (утилита Goose):
// 202312051200_create_bookings_table.go
func Up(tx *sql.Tx) error {
_, err := tx.Exec(`
CREATE TABLE bookings (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
slot_id UUID REFERENCES time_slots(id),
status VARCHAR(20) NOT NULL
);
CREATE INDEX idx_booking_user ON bookings(user_id);
`)
return err
}
- Для сложных фич создавались ADR (Architecture Decision Records):
- Процесс разработки
-
Спринты по 2 недели с чётким определением Definition of Done:
- Код покрыт юнит-тестами (минимум 80% по ключевым пакетам)
- Интеграционные тесты для основных сценариев
- Документация Swagger обновлена
- Проведён нагрузочный тест для высоконагруженных эндпоинтов
-
Пример workflow для задачи:
graph LR
A[Analysis] --> B[Tech Design]
B --> C[Implementation]
C --> D[Code Review]
D --> E[QA]
E --> F[Deploy] -
Code Review с акцентом на:
- Конкурентную безопасность (проверка data races через
-raceфлаг) - Эффективность SQL-запросов:
EXPLAIN ANALYZE
SELECT * FROM bookings
WHERE user_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
AND created_at > NOW() - INTERVAL '7 days'; - Соблюдение принципов SOLID (особенно Dependency Inversion)
- Конкурентную безопасность (проверка data races через
- Автоматизация
- CI/CD Pipeline:
stages:
- test
- build
- deploy
go_test:
stage: test
script:
- go vet ./...
- staticcheck ./...
- go test -race -covermode=atomic -coverprofile=coverage.out ./...
docker_build:
stage: build
only:
- merge_requests
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
deploy_staging:
stage: deploy
environment: staging
script:
- kubectl set image deployment/booking-service booking=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- Контроль качества
- Статический анализ:
go vetдля базовых проверокstaticcheckдля выявления антипаттернов
- Динамический анализ:
- Fuzz-тесты для обработки некорректных входных данных:
func FuzzParseDate(f *testing.F) {
f.Add("2023-13-01")
f.Fuzz(func(t *testing.T, s string) {
if _, err := time.Parse("2006-01-02", s); err == nil {
t.Logf("Valid date: %s", s)
}
})
}
- Fuzz-тесты для обработки некорректных входных данных:
- Нагрузочное тестирование с использованием wrk2:
wrk2 -t4 -c100 -d60s -R1000 http://localhost:8080/api/bookings
- Пост-релизные активности
- Мониторинг через Prometheus + Grafana:
- Ключевые метрики: latency, error rate, saturation
- Логирование структурированными логами (zap/slog):
logger.Info("booking created",
slog.String("booking_id", id.String()),
slog.Duration("duration", time.Since(start)),
) - Постмортемы для инцидентов с фокусом на предотвращение рецидивов
Рекомендация: Для бэкенд-позиций обязательно упоминайте технические детали процессов (типы тестирования, инструменты статического анализа, стратегии деплоя). Это демонстрирует зрелость подходов к разработке.#### Вопрос 5. Опишите процесс работы над задачами.
Таймкод: 00:03:28
Ответ собеседника: Неполный. Двухнедельные спринты с последующей проверкой тимлидом, доработки при необходимости. Не упомянуты процессы аналитики и проектирования.
Правильный ответ: Полноценный цикл разработки включал следующие этапы:
1. Анализ требований и проектирование
- Event Storming сессии для декомпозиции бизнес-процессов на доменные события:
[Команда] UserRequestsBooking →
[Событие] BookingRequested →
[Политика] ValidateSlotAvailability - Техническое проектирование с документированием решений в ADR (Architecture Decision Records):
## Выбор между Gin и Echo
Решение: Gin Framework
Причины:
- Более производительный роутинг (benchmark показал 15% прирост)
- Широкая экосистема middleware
- Поддержка валидации через binding - Прототипирование API с использованием OpenAPI 3.0:
/api/bookings:
post:
summary: Create new booking
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BookingRequest'
responses:
'201':
description: Created
2. Детализация задач
- Критерии приемки (Acceptance Criteria) для каждой задачи:
GIVEN пользователь с валидным токеном
WHEN запрашивается список доступных слотов
THEN возвращаются только слоты текущего города пользователя
AND слоты отсортированы по времени начала - Оценка сложности через планирование покера с учётом:
- Рисков конкурентного доступа
- Интеграций с внешними сервисами
- Необходимости новых миграций БД
3. Реализация
- Шаблон Feature Branch:
git checkout -b feature/booking-validation - Принципы написания кода:
- Закон Деметры для уменьшения связанности
// Плохо: user.GetProfile().GetAddress().City
// Хорошо: user.City()- Инкапсуляция бизнес-правил в доменных объектах:
func (b *Booking) Validate() error {
if b.User.Rating < 4.0 && b.Slot.Price > 1000 {
return ErrLowRatingForPremiumSlot
}
return nil
} - Инкрементальные коммиты с семантическими сообщениями:
feat(booking): add concurrent slot reservation
fix(payment): handle idempotency key collisions
4. Контроль качества
- Многоуровневое тестирование:
// Юнит-тест бизнес-логики
func TestBookingConflict(t *testing.T) {
repo := NewInMemoryRepo()
svc := NewBookingService(repo)
// Создаём первый слот
svc.CreateSlot("2023-01-01 10:00")
// Попытка создания пересекающегося слота
err := svc.CreateSlot("2023-01-01 10:30")
assert.ErrorIs(t, err, ErrSlotConflict)
}
// Интеграционный тест с реальной БД
func TestBookingDBIntegration(t *testing.T) {
db := testutil.SetupTestDB(t)
defer db.Close()
repo := NewPostgresRepo(db)
// ...тестовые сценарии
} - Статический анализ:
golangci-lint run --enable-all - Профилирование критических участков:
import _ "net/http/pprof"
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
5. Деплой и мониторинг
- Canary-релизы:
# Kubernetes Deployment
strategy:
canary:
steps:
- setWeight: 20
- pause: {duration: 2m}
- setWeight: 100 - Мониторинг бизнес-метрик:
# Количество успешных бронирований
sum(rate(booking_success_total[5m])) by (venue) - Трассировка запросов через Jaeger:
tr := otel.GetTracerProvider().Tracer("booking-service")
ctx, span := tr.Start(ctx, "CreateBooking")
defer span.End()
6. Пострелизные активности
- Анализ производительности:
SELECT query, calls, total_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10; - Ретроспективы с фокусом на улучшения:
- Внедрение DORA-метрик (Deployment Frequency, Lead Time)
- Автоматизация рутинных операций через скрипты Go
Рекомендация: Для позиций уровня Senior+ делайте акцент на архитектурных решениях, покажите глубокое понимание полного жизненного цикла задачи — от анализа до эксплуатации. Упоминание конкретных инструментов и метрик повышает доверие к опыту.
Вопрос 6. Присутствовали ли в команде дизайнер и бизнес-аналитик?
Таймкод: 00:04:12
Ответ собеседника: Неполный. Дизайнера не было, работали по готовым макетам (источник макетов не указан). Аналитики в команде не было.
Правильный ответ: Отсутствие этих ролей компенсировалось следующими практиками:
1. Работа с дизайном
- Источник макетов: Использовали шаблоны Material UI с кастомизацией под бренд. Пример структуры компонентов:
// React-компонент для выбора времени
<TimePicker
ampm={false}
defaultValue={dayjs('2023-11-18T10:00')}
slotProps={{ textField: { variant: 'outlined' } }}
/> - Процесс согласования:
- Коллаборация через Figma (готовые прототипы от заказчика)
- Валидация UX через User Story Mapping с владельцем продукта
- Интеграция дизайн-системы в Storybook для фронтенда
2. Компенсация отсутствия аналитика
- Сбор требований:
Разработчики напрямую общались с Product Owner через событийные воркшопы:Пример сессии:
Цель: Уменьшить количество отмен бронирований
Метрика: Увеличить conversion rate с 35% до 50%
Решение:
- Добавить напоминания за 2 часа (SMS/email)
- Ввести систему рейтинга пользователей - Техническая аналитика:
Самостоятельно проектировали событийную модель для сбора метрик:CREATE TABLE booking_events (
event_id UUID PRIMARY KEY,
event_type VARCHAR(50) NOT NULL, -- 'booking_created', 'reminder_sent'
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
); - Приоритизация:
Использовали RICE-модель для оценки задач:Reach (охват): 1000 пользователей/мес
Impact: +15% к конверсии
Confidence: 80%
Effort: 3 спринта
3. Инструменты замены аналитика
- SQL-аналитика:
-- Поиск узких мест в бронировании
SELECT
EXTRACT(HOUR FROM created_at) AS hour,
COUNT(*) FILTER (WHERE status = 'success') AS success,
COUNT(*) FILTER (WHERE status = 'failed') AS failed
FROM bookings
GROUP BY 1
ORDER BY failed DESC
LIMIT 5; - Go-сервис для сбора метрик:
func (t *Tracker) TrackEvent(ctx context.Context, event Event) error {
// Асинхронная запись в Kafka
go func() {
msg := &kafka.Message{
Key: []byte(event.UserID),
Value: event.Bytes(),
}
if err := t.producer.Send(msg); err != nil {
log.Printf("Failed to send event: %v", err)
}
}()
return nil
} - Дашборды в Grafana:
- Конверсия по этапам воронки
- Топ ошибок API по HTTP-кодам
- 95-й перцентиль времени ответа
4. Коммуникационные практики
- Трехэтапные уточнения для сложных задач:
- Обсуждение с PO: бизнес-цель и KPI
- Технический брифинг: оценки рисков
- Дизайн-ревью: валидация архитектуры
- Пример согласования фичи:
## Фича: Отмена бронирования
Бизнес-требование: Пользователи могут отменять бронь за 24 часа
Технические нюансы:
- Возврат средств через Stripe API
- Уведомление следующего в очереди (если есть)
- Обновление кэша слотов в Redis
5. Проектные решения при отсутствии ролей
- Доменно-ориентированное проектирование:
type BookingService struct {
repo BookingRepository
paymentClient PaymentGateway
notifier NotificationService
}
func (s *BookingService) CancelBooking(id string) error {
booking, err := s.repo.Get(id)
if err != nil { /* ... */ }
// Проверка бизнес-правил
if !booking.CanBeCancelled() {
return ErrCancellationNotAllowed
}
// Возврат платежа
if err := s.paymentClient.Refund(booking.TransactionID); err != nil {
return fmt.Errorf("refund failed: %w", err)
}
// Освобождение слота
return s.repo.UpdateStatus(id, "cancelled")
} - Контрактное тестирование с фронтендом через Pact:
// Consumer test
pact.VerifyConsumer(t, func() error {
client := BookingClient{URL: pact.Server.PactURL}
return client.GetBooking("123")
}, pact.SubPact{
Consumer: "frontend",
Provider: "booking-service",
})
Рекомендация: Для Senior-разработчиков критически важно показать, как вы компенсировали недостающие роли через технические решения. Акцентируйте внимание на:
- Системах сбора требований
- Инструментах анализа данных
- Архитектурных паттернах, снижающих потребность в постоянной коммуникации
Вопрос 7. Какие технологии вы использовали в проекте?
Таймкод: 00:06:21
Ответ собеседника: Правильный. TypeScript, React, Next.js, React Query, Axios, Material UI.
Правильный ответ: В проекте применялся комплексный стек технологий с акцентом на производительность и поддерживаемость. Вот детализация по категориям:
1. Бэкенд (Go)
-
Фреймворки:
Ginдля HTTP-роутинга (выбор обусловлен производительностью)gRPCдля межсервисного взаимодействияWorkflowдля долгих процессов (например, обработки платежей)
-
Базы данных:
- PostgreSQL с расширением
pg_partmanдля партиционирования - Redis для кэширования и очередей:
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
err := client.Set(ctx, "key", "value", 10*time.Minute).Err()
- PostgreSQL с расширением
-
Миграции:
Gooseс версионированием миграций:goose postgres "user=postgres dbname=booking sslmode=disable" up
-
Тестирование:
testifyдля assertions и моковtestcontainers-goдля интеграционных тестов с БД
2. Инфраструктура
-
Оркестрация:
- Kubernetes (локально Minikube, продакшн EKS)
- Helm для управления чартами
-
Мониторинг:
- Prometheus + Grafana (кастомные метрики через
promhttp):http.Handle("/metrics", promhttp.Handler()) - Jaeger для распределенной трассировки
- Prometheus + Grafana (кастомные метрики через
-
CI/CD:
- GitLab CI с многоступенчатой сборкой:
build:
stage: build
script:
- CGO_ENABLED=0 GOOS=linux go build -o app .
artifacts:
paths:
- app
- GitLab CI с многоступенчатой сборкой:
3. Фронтенд
-
Основной стек:
- TypeScript 5.0 со строгим линтингом (ESLint + TypeCheck)
- Next.js 14 с App Router для SSR
- Zustand для state-менеджмента
-
Оптимизации:
- Динамический импорт компонентов:
const DynamicMap = dynamic(() => import('./Map'), { ssr: false }) - Кэширование через React Query:
const { data } = useQuery({
queryKey: ['bookings'],
queryFn: fetchBookings,
staleTime: 60_000
})
- Динамический импорт компонентов:
4. Взаимодействие сервисов
-
Асинхронная коммуникация:
- RabbitMQ с подтверждениями (ack/nack)
- Dead Letter Queues для обработки сбоев
-
Схема взаимодействия:
graph LR
A[API Gateway] --> B[Auth Service]
A --> C[Booking Service]
C --> D[(PostgreSQL)]
C --> E[RabbitMQ]
E --> F[Notification Service]
5. Инструменты разработки
-
Локальное окружение:
- Docker Compose для поднятия зависимостей
- Skaffold для hot-reload в Kubernetes
-
Профилирование:
- pprof для анализа производительности:
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile - Flamegraph для поиска узких мест
- pprof для анализа производительности:
6. Безопасность
-
Аутентификация:
- JWT с доступом по ролям (RBAC)
- Хеширование паролей через bcrypt:
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
-
Защита API:
- Rate limiting через Redis:
func RateLimiter(key string, limit int) gin.HandlerFunc {
return func(c *gin.Context) {
count, _ := client.Incr(ctx, key).Result()
if count > limit {
c.AbortWithStatus(429)
}
}
}
- Rate limiting через Redis:
Рекомендация: Для бэкенд-ролей структурируйте стек по категориям (базы данных, инфраструктура, безопасность), акцентируя темы, релевантные вакансии. Упоминайте конкретные версии (PostgreSQL 15, Go 1.21) и причины выбора технологий.
Вопрос 8. Какие типы данных существуют в JavaScript?
Таймкод: 00:07:22
Ответ собеседника: Правильный. 7 примитивов (number, string, boolean, bigint, symbol, null, undefined) и объекты (включая массивы и функции).
Правильный ответ: Система типов в JavaScript имеет глубокие особенности, которые важно понимать для написания надежного кода:
1. Примитивные типы (7 видов)
- Особенности:
- Иммутабельность (при "изменении" создается новая копия)
- Передача по значению
- Не имеют методов (автоупаковка в объекты при вызове методов)
- Полный список:
// Проверка через typeof
typeof 42; // 'number' (включая NaN, Infinity)
typeof 'text'; // 'string'
typeof true; // 'boolean'
typeof 10n; // 'bigint' (ES2020)
typeof Symbol(); // 'symbol' (ES2015)
typeof undefined; // 'undefined'
// Особый случай (историческая ошибка языка)
typeof null; // 'object' (на самом деле примитив!)
2. Объектные типы
-
Особенности:
- Мутабельность
- Передача по ссылке
- Наличие прототипов
- Могут иметь методы
-
Основные виды:
// Стандартные объекты
const obj = { a: 1 };
const arr = [1, 2];
const func = () => {};
const date = new Date();
// Проверка
Array.isArray(arr); // true
func instanceof Function; // true
3. Специальные случаи
-
Отличие
nullотundefined:undefined: переменная объявлена, но значение не присвоеноnull: явное указание на отсутствие значения
let a; // undefined
let b = null; // null
typeof c; // 'undefined' (необъявленная переменная) -
Автоупаковка примитивов:
// Примитив временно становится объектом
'text'.toUpperCase(); // Автоматическое создание String-обертки
4. Новые типы в современных стандартах
-
BigInt:
const big = 9007199254740991n;
console.log(big + 1n); // 9007199254740992n (работает с числами > 2^53) -
Symbol:
const uid = Symbol('unique');
const obj = { [uid]: 'id123' };
Object.keys(obj); // [] - символы не перечисляются
5. Структуры данных (технически объекты)
- Коллекции:
// Отличия от обычных объектов
const map = new Map();
map.set('key', 'value');
const set = new Set([1, 2, 3]);
// Буферы для бинарных данных
const buffer = new ArrayBuffer(16);
6. Проверка типов: лучшие практики
-
Точная проверка примитивов:
function isNull(value) {
return value === null; // Единственный надежный способ
}
Object.is(NaN, NaN); // true (в отличие от ===) -
Проверка объектов:
function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
// Отличие массивов от объектов
Array.isArray([1, 2]); // true
7. Производительность и память
- Оптимизации движка:
- Примитивы хранятся в стеке (быстрый доступ)
- Объекты - в куче (управление через ссылки)
- Пример с памятью:
// Плохо: создание новых объектов в цикле
for (let i = 0; i < 1e6; i++) {
const obj = { id: i }; // 1 млн объектов в куче
}
// Лучше: использовать примитивы
const cache = new Map();
for (let i = 0; i < 1e6; i++) {
cache.set(i, i); // Эффективнее по памяти
}
8. Особенности в сравнениях
-
Слабая типизация:
'5' == 5; // true (неявное преобразование)
'5' === 5; // false (строгое сравнение) -
Таблица преобразований:
Number(true); // 1
Number(''); // 0
Boolean([]); // true (пустой массив)
Boolean({}); // true (пустой объект)
Рекомендации:
- Всегда используйте
===вместо== - Для проверки на
null/undefinedиспользуйтеvalue == null - При работе с большими структурами данных выбирайте TypedArrays вместо обычных массивов
- Используйте
Object.freeze()для защиты объектов от изменений
Вопрос 9. В чем различие между null и undefined в JavaScript?
Таймкод: 00:08:26
Ответ собеседника: Правильный. null — явное пустое значение, undefined — переменная объявлена, но не инициализирована.
Правильный ответ: Хотя оба значения обозначают отсутствие данных, между ними есть фундаментальные различия:
1. Семантическое значение
-
undefined:- Переменная объявлена, но значение не присвоено
- Свойство объекта не существует
- Функция не вернула значение
- Явное присваивание
undefined
-
null:- Явное указание на "пустое" или "несуществующее" значение
- Часто используется в API для обозначения намеренного отсутствия значения
2. Поведение в коде
-
Проверка типа:
typeof undefined; // 'undefined'
typeof null; // 'object' (историческая ошибка в языке) -
Сравнения:
null == undefined; // true (абстрактное сравнение)
null === undefined; // false (строгое сравнение) -
Приведение типов:
Number(undefined); // NaN
Number(null); // 0
Boolean(undefined); // false
Boolean(null); // false
3. Использование в функциях
-
Параметры по умолчанию:
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(undefined); // Hello, Guest!
greet(null); // Hello, null! -
Возвращаемые значения:
function findUser(id) {
const user = db.query(id);
return user || null; // Явное возвращение null при отсутствии
}
4. Особенности JSON
- Сериализация:
JSON.stringify({ a: undefined, b: null });
// '{"b":null}' (undefined-свойства опускаются)
5. Лучшие практики
-
Инициализация переменных:
- Не оставляйте переменные
undefined— явно инициализируйте их:let count = 0; // Вместо let count;
- Не оставляйте переменные
-
Работа с объектами:
- Для удаления свойств используйте
delete, а не присваиваниеundefined:const obj = { a: 1, b: 2 };
delete obj.a; // obj теперь { b: 2 }
obj.b = undefined; // obj { b: undefined } (антипаттерн)
- Для удаления свойств используйте
-
Проверки:
// Проверка на оба значения
if (value == null) { /* value === null || value === undefined */ }
// Опциональная цепочка
const name = user?.profile?.name ?? 'Anonymous';
6. Производительность
-
Оптимизации движка:
- V8 использует разные внутренние представления:
undefined: специальное значение-маркерnull: указатель на нулевой адрес
- V8 использует разные внутренние представления:
-
Пример с памятью:
// Более эффективно при итерациях
const sparseArray = [1, , 3]; // Элемент [1] = undefined (дырка)
sparseArray[1] === undefined; // true
7. Исторический контекст
- Почему
typeof null === 'object':- Ошибка в первой реализации JavaScript (1995)
- Сохранена для обратной совместимости
- Предложение изменить поведение было отклонено TC39
8. Использование в TypeScript
- Строгая типизация:
let a: string | null = null; // Явное указание nullable типа
let b?: string; // Эквивалент string | undefined
function log(msg: string | undefined) { ... }
Рекомендации:
- Используйте
nullдля обозначения преднамеренного отсутствия значения - Избегайте явного присваивания
undefined - В API предпочитайте возвращать
nullдля отсутствующих данных - В TypeScript явно аннотируйте типы с
null/undefined#### Вопрос 10. В чем отличиеnullотundefinedв JavaScript?
Таймкод: 00:08:26
Ответ собеседника: Правильный. null — явное пустое значение, undefined — переменная объявлена, но не инициализирована.
Правильный ответ: Хотя оба значения обозначают отсутствие данных, между ними есть фундаментальные различия:
1. Семантическое значение
-
undefined:- Переменная объявлена, но значение не присвоено
- Свойство объекта не существует
- Функция не вернула значение
- Явное присваивание
undefined
-
null:- Явное указание на "пустое" или "несуществующее" значение
- Часто используется в API для обозначения намеренного отсутствия значения
2. Поведение в коде
-
Проверка типа:
typeof undefined; // 'undefined'
typeof null; // 'object' (историческая ошибка в языке) -
Сравнения:
null == undefined; // true (абстрактное сравнение)
null === undefined; // false (строгое сравнение) -
Приведение типов:
Number(undefined); // NaN
Number(null); // 0
Boolean(undefined); // false
Boolean(null); // false
3. Использование в функциях
-
Параметры по умолчанию:
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(undefined); // Hello, Guest!
greet(null); // Hello, null! -
Возвращаемые значения:
function findUser(id) {
const user = db.query(id);
return user || null; // Явное возвращение null при отсутствии
}
4. Особенности JSON
- Сериализация:
JSON.stringify({ a: undefined, b: null });
// '{"b":null}' (undefined-свойства опускаются)
5. Лучшие практики
-
Инициализация переменных:
- Не оставляйте переменные
undefined— явно инициализируйте их:let count = 0; // Вместо let count;
- Не оставляйте переменные
-
Работа с объектами:
- Для удаления свойств используйте
delete, а не присваиваниеundefined:const obj = { a: 1, b: 2 };
delete obj.a; // obj теперь { b: 2 }
obj.b = undefined; // obj { b: undefined } (антипаттерн)
- Для удаления свойств используйте
-
Проверки:
// Проверка на оба значения
if (value == null) { /* value === null || value === undefined */ }
// Опциональная цепочка
const name = user?.profile?.name ?? 'Anonymous';
6. Производительность
-
Оптимизации движка:
- V8 использует разные внутренние представления:
undefined: специальное значение-маркерnull: указатель на нулевой адрес
- V8 использует разные внутренние представления:
-
Пример с памятью:
// Более эффективно при итерациях
const sparseArray = [1, , 3]; // Элемент [1] = undefined (дырка)
sparseArray[1] === undefined; // true
7. Исторический контекст
- Почему
typeof null === 'object':- Ошибка в первой реализации JavaScript (1995)
- Сохранена для обратной совместимости
- Предложение изменить поведение было отклонено TC39
8. Использование в TypeScript
- Строгая типизация:
let a: string | null = null; // Явное указание nullable типа
let b?: string; // Эквивалент string | undefined
function log(msg: string | undefined) { ... }
Рекомендации:
- Используйте
nullдля обозначения преднамеренного отсутствия значения - Избегайте явного присваивания
undefined - В API предпочитайте возвращать
nullдля отсутствующих данных - В TypeScript явно аннотируйте типы с
null/undefined
Вопрос 11. Что такое распространение событий (Event Propagation) в JavaScript?
Таймкод: 00:08:46
Ответ собеседника: Неполный. Описание bubbling (всплытие от целевого элемента к родителям), но не упомянуты фазы capturing и target.
Правильный ответ: Механизм распространения событий в DOM состоит из трёх фаз, обеспечивающих гибкость в обработке взаимодействий:
1. Три фазы распространения
- Capture Phase (Фаза захвата)
Событие движется от корня документа к целевому элементу:Window → Document → <html> → <body> → ... → Целевой элемент - Target Phase (Целевая фаза)
Событие достигло целевого элемента (event.target). - Bubble Phase (Фаза всплытия)
Событие поднимается обратно к корню документа:Целевой элемент → ... → <body> → <html> → Document → Window
2. Настройка обработчиков
-
Добавление слушателя на фазу захвата:
element.addEventListener('click', handler, { capture: true });
// Или сокращенно
element.addEventListener('click', handler, true); -
Обычный обработчик (на фазу всплытия):
element.addEventListener('click', handler);
// или с явным указанием
element.addEventListener('click', handler, { capture: false });
3. Пример полного цикла
<div id="grandparent">
<div id="parent">
<button id="child">Click</button>
</div>
</div>
<script>
document.getElementById('grandparent').addEventListener('click', () => {
console.log('Grandparent (capture)');
}, true);
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent (bubble)');
});
document.getElementById('child').addEventListener('click', (e) => {
console.log('Child (target)');
});
</script>
Вывод при клике на кнопку:
Grandparent (capture)
Child (target)
Parent (bubble)
4. Управление распространением
-
Остановка распространения:
function handler(e) {
e.stopPropagation(); // Прекращает движение на текущей фазе
// e.stopImmediatePropagation() - также предотвращает вызов других обработчиков на этом элементе
} -
Отмена действия по умолчанию:
e.preventDefault(); // Например, для предотвращения отправки формы
5. События без фазы всплытия Некоторые события не всплывают (но имеют фазу захвата):
focus/blurload/unloadmouseenter/mouseleave
Для их обработки используйте:
// Вместо blur
element.addEventListener('focusout', handler);
// Или параметр capture
element.addEventListener('focus', handler, true);
6. Делегирование событий Паттерн для эффективной обработки:
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.matches('li.item')) {
console.log('Item clicked:', e.target.dataset.id);
}
});
Преимущества:
- Работает для динамически добавляемых элементов
- Меньшее количество обработчиков в памяти
7. Производительность
- Опасности:
- Слишком глубокие деревья обработчиков
- Неконтролируемое всплытие (может привести к неожиданным срабатываниям)
- Оптимизации:
- Используйте делегирование
- Удаляйте ненужные обработчики через
removeEventListener - Избегайте
stopPropagation()в библиотечных компонентах
8. Внутренняя реализация
- Event Loop: Обработка событий происходит в рамках задачи (task)
- Синтетические события в React: обёртка над нативными событиями с пулом объектов
Рекомендации:
- Для сложных UI используйте делегирование вместо множества обработчиков
- Чётко разделяйте логику захвата и всплытия
- В библиотечных компонентах избегайте глобальных обработчиков на
document/windowбез необходимости
Вопрос 11. Что такое распространение событий (Event Propagation) в JavaScript?
Таймкод: 00:08:46
Ответ собеседника: Неполный. Описание bubbling (всплытие от целевого элемента к родителям), но не упомянуты фазы capturing и target.
Правильный ответ: Механизм распространения событий в DOM включает три взаимосвязанные фазы, обеспечивающие детальный контроль над обработкой событий:
1. Фазы жизненного цикла события
-
Capture Phase (Фаза захвата)
Событие движется сверху вниз от корневого элемента (window) до целевого элемента:window → document → <html> → <body> → родительские элементы → целевой элемент- Используется редко, но критична для перехвата событий до их обработки
element.addEventListener('click', handler, { capture: true }); -
Target Phase (Целевая фаза)
Событие достигло элемента, на котором произошло действие (event.target):- Все обработчики на целевом элементе выполняются в порядке добавления
button.addEventListener('click', () => {
console.log('Target handler 1');
});
button.addEventListener('click', () => {
console.log('Target handler 2');
}); -
Bubble Phase (Фаза всплытия)
Событие поднимается обратно к корню документа:целевой элемент → родительские элементы → <body> → <html> → document → windowparent.addEventListener('click', () => {
console.log('Bubbling handler');
});
2. Полный пример жизненного цикла
<div id="grandparent">
<div id="parent">
<button id="child">Click me</button>
</div>
</div>
<script>
const logPhase = (name, capture = false) =>
console.log(`${name} (${capture ? 'capture' : 'bubble'})`);
document.getElementById('grandparent').addEventListener('click', () => logPhase('Grandparent'), true);
document.getElementById('parent').addEventListener('click', () => logPhase('Parent'), true);
document.getElementById('child').addEventListener('click', () => logPhase('Child target'));
document.getElementById('parent').addEventListener('click', () => logPhase('Parent'));
document.getElementById('grandparent').addEventListener('click', () => logPhase('Grandparent'));
</script>
Вывод при клике на кнопку:
Grandparent (capture)
Parent (capture)
Child target
Parent (bubble)
Grandparent (bubble)
3. Управление потоком событий
-
event.stopPropagation()
Прерывает распространение на текущей фазе:parent.addEventListener('click', (e) => {
e.stopPropagation(); // Запрещает всплытие выше parent
}, true); // Если true - остановит фазу захвата -
event.stopImmediatePropagation()
Дополнительно предотвращает вызов других обработчиков на этом же элементе:button.addEventListener('click', (e) => {
e.stopImmediatePropagation(); // Следующие обработчики на button не вызовутся
}); -
event.preventDefault()
Отменяет стандартное поведение браузера (например, отправку формы):form.addEventListener('submit', (e) => {
if (!validate()) e.preventDefault();
});
4. События без всплытия Некоторые события не имеют фазы всплытия:
focus/blur(используйтеfocusin/focusout)load/unloadmouseenter/mouseleave
Решение:
// Для обработки фокуса с всплытием
container.addEventListener('focusin', handleFocus);
5. Делегирование событий Паттерн для оптимизации обработки множества элементов:
document.querySelector('.list').addEventListener('click', (e) => {
if (e.target.closest('.item')) {
console.log('Clicked item:', e.target.dataset.id);
}
});
Преимущества:
- Работает для динамически добавляемых элементов
- Снижает количество обработчиков
- Уменьшает потребление памяти
6. Глубокий анализ производительности
-
Влияние на рендеринг:
Долгие обработчики блокируют основной поток, вызывая лаги интерфейса. Решение:element.addEventListener('click', (e) => {
setTimeout(() => { /* Тяжелые вычисления */ }, 0);
}); -
Пассивные обработчики:
Для событий типаtouchmove/wheelиспользуйте флагpassive:element.addEventListener('touchmove', handler, {
passive: true // Браузер не будет ждать preventDefault()
});
7. Особенности в React
- Синтетические события:
React использует пул событий для производительности:function handleClick(e) {
e.persist(); // Для асинхронного доступа к событию
setTimeout(() => {
console.log(e.target); // Работает только с persist()
}, 100);
} - Делегирование:
Все события в React делегируются на корневой элемент черезaddEventListener.
8. Отладка событий
- Мониторинг всех событий:
monitorEvents(document.body, 'click'); // Chrome DevTools - Визуализация:
element.addEventListener('click', (e) => {
console.log(e.eventPhase); // 1-capture, 2-target, 3-bubble
});
Рекомендации:
- Для модальных окон используйте обработку на фазе захвата
- Избегайте глобальных
stopPropagation()в библиотеках - Используйте делегирование для таблиц, списков и динамических UI
Вопрос 12. Назовите фазы распространения событий в DOM.
Таймкод: 00:09:24
Ответ собеседника: Неполный. Упомянуты только 2 фазы (погружение и всплытие), не названа фаза target.
Правильный ответ: Полный цикл распространения событий включает три четко различимые фазы:
1. Детализация фаз
-
Capture Phase (Фаза захвата / погружение)
- Событие движется сверху вниз от
windowдо целевого элемента - Порядок:
window → document → <html> → <body> → родительские элементы → event.target
// Обработчик на фазе захвата
parent.addEventListener('click', handler, { capture: true }); - Событие движется сверху вниз от
-
Target Phase (Фаза цели)
- Событие достигло элемента, на котором произошло действие (
event.target) - Все обработчики на целевом элементе выполняются в порядке их добавления
button.addEventListener('click', () => console.log('Handler 1'));
button.addEventListener('click', () => console.log('Handler 2')); - Событие достигло элемента, на котором произошло действие (
-
Bubble Phase (Фаза всплытия)
- Событие поднимается снизу вверх обратно к корню
- Порядок:
event.target → родительские элементы → <body> → <html> → document → window
// Стандартный обработчик (по умолчанию на фазе всплытия)
parent.addEventListener('click', handler);
2. Визуализация полного цикла
<div id="outer">
<div id="middle">
<button id="inner">Кликни</button>
</div>
</div>
<script>
const elements = ['outer', 'middle', 'inner'];
elements.forEach(id => {
const el = document.getElementById(id);
// Обработчики на захват
el.addEventListener('click', () => console.log(`${id} capture`), true);
// Обработчики на всплытие
el.addEventListener('click', () => console.log(`${id} bubble`));
});
</script>
Вывод при клике на кнопку:
outer capture
middle capture
inner capture (target phase)
inner bubble (target phase)
middle bubble
outer bubble
3. Ключевые особенности
-
event.eventPhase
Свойство возвращает текущую фазу:element.addEventListener('click', (e) => {
console.log(e.eventPhase); // 1-CAPTURING, 2-AT_TARGET, 3-BUBBLING
}); -
События без всплытия
Некоторые события (например,focus,blur) не всплывают. Для их обработки:// Используем фазу захвата
form.addEventListener('focus', validateInput, true);
// Или специальные события с всплытием
form.addEventListener('focusin', validateInput);
4. Управление потоком событий
| Метод | Воздействие |
|---|---|
event.stopPropagation() | Останавливает дальнейшее распространение на текущей фазе |
event.stopImmediatePropagation() | + предотвращает вызов других обработчиков на этом же элементе |
event.preventDefault() | Отменяет стандартное поведение браузера (отправка формы, переход по ссылке) |
5. Практическое применение
Делегирование событий
Паттерн для обработки динамических элементов через общего родителя:
document.querySelector('.table').addEventListener('click', (e) => {
const row = e.target.closest('tr[data-id]');
if (row) {
console.log('Selected row:', row.dataset.id);
}
});
Оптимизация производительности
- Один обработчик вместо N (экономия памяти)
- Работает для элементов, добавленных позже
6. Особенности в современных фреймворках React
- Синтетические события: делегирование на корневой элемент
- Обработчики всегда вызываются на фазе всплытия
- Для перехвата на фазе захвата используйте суффикс
Capture:<div onClickCapture={handleCapture}>...</div>
Vue
- Модификатор
.captureдля перехвата:<div v-on:click.capture="handleCapture">...</div>
Рекомендации:
- Для глобальных перехватчиков (логирование, аналитика) используйте фазу захвата
- Избегайте
stopPropagation()в библиотечных компонентах — это ломает ожидаемое поведение - В сложных UI всегда используйте делегирование событий для динамических элементов
Вопрос 13. В чем разница между preventDefault() и stopPropagation()?
Таймкод: 00:10:22
Ответ собеседника: Правильный. preventDefault() отменяет действие по умолчанию, stopPropagation() останавливает всплытие события.
Правильный ответ: Хотя оба метода используются для управления поведением событий, они решают принципиально разные задачи:
1. event.preventDefault()
-
Назначение:
Отменяет стандартное поведение браузера, связанное с событием. -
Примеры действий по умолчанию:
- Отправка формы (
submitсобытие) - Переход по ссылке (
clickна<a>) - Открытие контекстного меню (
contextmenu) - Ввод символа в текстовое поле (
keypress)
- Отправка формы (
-
Использование:
document.querySelector('a').addEventListener('click', (e) => {
e.preventDefault(); // Блокирует переход по ссылке
console.log('Навигация отменена');
}); -
Особенности:
- Не останавливает распространение события
- Событие продолжает всплывать
- Можно проверить статус через
event.defaultPrevented
2. event.stopPropagation()
-
Назначение:
Останавливает дальнейшее распространение события в DOM (фазы захвата и всплытия). -
Пример:
<div id="parent">
<button id="child">Click</button>
</div>
<script>
parent.addEventListener('click', () => console.log('Parent clicked'));
child.addEventListener('click', (e) => {
e.stopPropagation(); // Предотвращает всплытие
console.log('Child clicked');
});
</script>Вывод: Только
Child clicked -
Особенности:
- Не отменяет действие по умолчанию
- Не влияет на другие обработчики на том же элементе
- Можно использовать как на фазе захвата, так и всплытия
3. event.stopImmediatePropagation()
-
Назначение:
Помимо остановки распространения, предотвращает вызов любых других обработчиков на текущем элементе. -
Пример:
element.addEventListener('click', (e) => {
e.stopImmediatePropagation();
console.log('Handler 1');
});
element.addEventListener('click', () => {
console.log('Handler 2'); // Никогда не выполнится
});
4. Сравнительная таблица
| Метод | Отменяет действие по умолчанию | Останавливает всплытие | Блокирует другие обработчики |
|---|---|---|---|
preventDefault() | Да | Нет | Нет |
stopPropagation() | Нет | Да | Нет |
stopImmediatePropagation() | Нет | Да | Да |
5. Комбинированное использование
form.addEventListener('submit', (e) => {
e.preventDefault(); // Блокируем отправку формы
e.stopPropagation(); // Останавливаем всплытие
// Кастомная обработка
fetch('/api', { method: 'POST' })
.then(handleResponse);
});
6. Глубокое понимание через примеры Сценарий 1: Меню с внешним кликом
document.addEventListener('click', closeMenu); // Закрыть меню при клике вне его
menuButton.addEventListener('click', (e) => {
e.stopPropagation(); // Предотвращает всплытие -> closeMenu не сработает
toggleMenu();
});
Сценарий 2: Валидация формы
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Блокируем отправку по Enter
validateField();
}
});
7. Особенности в React
- Синтетические события:
Оба метода работают аналогично нативным, но:function handleClick(e) {
e.preventDefault(); // Блокирует действие по умолчанию
e.stopPropagation(); // Останавливает всплытие в React-дереве
} - Важно: В React события делегируются, поэтому
stopPropagation()не останавливает нативные события наdocument.
Рекомендации:
- Используйте
preventDefault()только когда нужно отменить стандартное поведение - Избегайте
stopPropagation()в библиотечных компонентах — это может сломать логику приложения - Для сложных сценариев комбинируйте методы осознанно
Вопрос 13. В чем разница между preventDefault() и stopPropagation()?
Таймкод: 00:10:22
Ответ собеседника: Правильный. preventDefault() отменяет действие по умолчанию, stopPropagation() останавливает всплытие события.
Правильный ответ: Хотя оба метода используются для управления событиями в JavaScript, они выполняют принципиально разные функции:
1. event.preventDefault()
Назначение:
Отменяет стандартное поведение браузера, связанное с событием. Не влияет на распространение события по DOM.
Когда использовать:
- Блокировка отправки формы при невалидных данных
- Предотвращение перехода по ссылке
- Запрет контекстного меню
Пример:
document.querySelector('form').addEventListener('submit', (e) => {
if (!isFormValid()) {
e.preventDefault(); // Отменяет отправку формы
showErrors();
}
});
Особенности:
- Проверить статус можно через
event.defaultPrevented - Не останавливает всплытие события
2. event.stopPropagation()
Назначение:
Останавливает дальнейшее распространение события через фазы захвата и всплытия. Не влияет на действие по умолчанию.
Когда использовать:
- Изоляция компонента (например, модального окна)
- Предотвращение конфликтов обработчиков разных уровней
Пример:
document.getElementById('modal').addEventListener('click', (e) => {
e.stopPropagation(); // Клики внутри модалки не достигнут обработчика фона
});
document.body.addEventListener('click', () => {
closeModal(); // Не сработает при клике внутри модалки
});
Особенности:
- Останавливает только распространение события
- Обработчики на текущем элементе все равно выполнятся
3. event.stopImmediatePropagation()
Назначение:
Более радикальная версия stopPropagation() — дополнительно предотвращает вызов других обработчиков на том же элементе.
Пример:
button.addEventListener('click', (e) => {
e.stopImmediatePropagation(); // Блокирует Handler 2
console.log('Handler 1');
});
button.addEventListener('click', () => {
console.log('Handler 2'); // Не выполнится
});
4. Сравнительная таблица
| Метод | Отмена поведения | Остановка распространения | Блокировка других обработчиков |
|---|---|---|---|
preventDefault() | Да | Нет | Нет |
stopPropagation() | Нет | Да | Нет |
stopImmediatePropagation() | Нет | Да | Да |
5. Комбинированное использование
link.addEventListener('click', (e) => {
e.preventDefault(); // Отменяем переход
e.stopPropagation(); // Останавливаем всплытие
fetchData().then(() => {
window.location = e.target.href; // Программный переход
});
});
6. Особенности в современных фреймворках React:
- Синтетические события объединяют нативные методы
e.preventDefault()работает как в DOMe.stopPropagation()останавливает распространение только в React-дереве
Vue:
- Модификаторы
.preventи.stopв директивеv-on:<form @submit.prevent="handleSubmit">
<div @click.stop="handleClick"></div>
7. Рекомендации по использованию
-
Избегайте глобального
stopPropagation()
Это может нарушить работу аналитики и других обработчиков. -
Проверяйте
defaultPrevented
В обработчиках верхнего уровня:document.addEventListener('click', (e) => {
if (e.defaultPrevented) return;
// Логика для необработанных событий
}); -
Для кастомных элементов используйте
dispatchEvent
Создавайте события с флагомcancelable: true:const event = new CustomEvent('my-event', {
cancelable: true
});
element.dispatchEvent(event);
if (event.defaultPrevented) {
// Обработка отмены
}
Итог:
preventDefault()— для управления поведением браузераstopPropagation()— для контроля потока событийstopImmediatePropagation()— для полного контроля на элементе
Вопрос 14. Чем отличаются var, let и const в JavaScript?
Таймкод: 00:11:21
Ответ собеседника: Правильный. var — устаревшее с функциональной областью видимости и поднятием (hoisting), let/const — блочная область видимости. const нельзя переопределять, кроме случаев с изменением содержимого объектов/массивов.
Правильный ответ: Различия между этими объявлениями критичны для написания надёжного кода. Рассмотрим детально:
1. Область видимости (Scope)
-
var— функциональная область видимости (или глобальная, если объявлена вне функции):function test() {
if (true) {
var x = 10;
}
console.log(x); // 10 (доступна вне блока)
} -
let/const— блочная область видимости (в пределах{}):if (true) {
let y = 20;
const z = 30;
}
console.log(y); // ReferenceError
console.log(z); // ReferenceError
2. Поднятие (Hoisting)
-
var— инициализируется какundefinedдо объявления:console.log(a); // undefined
var a = 5; -
let/const— тоже поднимаются, но остаются в "временной мёртвой зоне" (TDZ):console.log(b); // ReferenceError
let b = 10;
3. Повторное объявление
-
var— позволяет переопределять в той же области:var c = 1;
var c = 2; // ОК -
let/const— запрещают повторное объявление:let d = 3;
let d = 4; // SyntaxError
const e = 5;
const e = 6; // SyntaxError
4. Иммутабельность
-
const— защищает от переприсваивания, но не от мутаций:const obj = { name: 'John' };
obj.name = 'Mike'; // ОК
obj = {}; // TypeError
const arr = [1, 2];
arr.push(3); // ОК
arr = [4, 5]; // TypeError -
Для настоящей иммутабельности используйте
Object.freeze():const frozen = Object.freeze({ value: 42 });
frozen.value = 100; // Тихий сбой в нестрогом режиме
5. Глобальные свойства
-
varв глобальной области создаёт свойстваwindow:var globalVar = 'test';
console.log(window.globalVar); // 'test' -
let/constне добавляют свойств вwindow:let localLet = 'value';
console.log(window.localLet); // undefined
6. Циклы и замыкания
-
varв циклах вызывает классическую проблему замыканий:for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
} -
letрешает проблему, создавая новую привязку на каждой итерации:for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 0, 1, 2
}
7. Рекомендации по использованию
- Всегда используйте
constпо умолчанию, если переменная не будет переприсвоена. - Используйте
letтолько когда значение должно изменяться. - Избегайте
varв современном коде (кроме особых случаев). - Для объектов/массивов:
- Используйте
constдля ссылки - Для иммутабельности применяйте копирование или библиотеки типа Immutable.js
- Используйте
8. Сравнительная таблица
| Особенность | var | let | const |
|---|---|---|---|
| Область видимости | Функция | Блок | Блок |
| Поднятие | Да (undefined) | Да (TDZ) | Да (TDZ) |
| Переопределение | Разрешено | Запрещено | Запрещено |
| Иммутабельность | Нет | Нет | Частичная |
| Глобальный объект | Добавляет | Не добавляет | Не добавляет |
| Циклы | Общая привязка | Новая привязка | Новая привязка |
Итог:
const— для константных ссылокlet— для изменяемых переменныхvar— legacy, требует особой осторожности
Вопрос 15. Что подвергается hoisting (поднятию) в JavaScript?
Таймкод: 00:12:47
Ответ собеседника: Правильный. Переменные var и function declarations (можно использовать до объявления).
Правильный ответ: Механизм hoisting влияет на разные типы объявлений по-разному. Вот полная картина:
1. Function Declarations (Объявления функций)
- Полностью поднимаются (и тело функции доступно сразу):
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
} - Особенность: Имеют приоритет над переменными при конфликте имён.
2. var Variables (Переменные var)
- Поднимается только объявление, инициализируется как
undefined:console.log(x); // undefined
var x = 10; - Эквивалентно:
var x; // Поднято
console.log(x); // undefined
x = 10;
3. let и const Variables
- Технически поднимаются, но попадают во временную мёртвую зону (TDZ):
console.log(y); // ReferenceError
let y = 20;
console.log(z); // ReferenceError
const z = 30; - Доступны только после объявления в блоке.
4. Function Expressions (Функциональные выражения)
- Зависят от типа переменной:
// С var
console.log(funcVar); // undefined
funcVar(); // TypeError: funcVar is not a function
var funcVar = function() {};
// С let/const
funcLet(); // ReferenceError
let funcLet = () => {};
5. Классы (Classes)
- Не поднимаются (аналогично
let/const):const obj = new MyClass(); // ReferenceError
class MyClass {}
6. Импорты (ES6 Modules)
- Поднимаются наверх модуля:
console.log(api); // Работает
import api from './api.js';
7. Приоритеты при поднятии Порядок приоритета (от высшего):
- Function Declarations
varVariables- Аргументы функции
Пример конфликта:
console.log(typeof greet); // "function"
var greet = "Hello";
function greet() {
return "Hi!";
}
console.log(typeof greet); // "string"
8. Лучшие практики
-
Всегда объявляйте переменные до использования
Избегайте зависимости от hoisting:// Плохо
console.log(count);
var count = 10;
// Хорошо
let count = 10;
console.log(count); -
Используйте
const/letвместоvar
Чтобы избежать TDZ и проблем с областью видимости. -
Размещайте функции в коде перед вызовами
Даже с учётом hoisting'а — для читаемости. -
Для экспрессивного кода используйте IIFE
Чтобы изолировать переменныеvar:(function() {
var tmp = calculate();
// ...
})();
Итог:
- Полностью поднимаются: Function Declarations
- Частично поднимаются:
var(инициализируются какundefined) - Недоступны до объявления:
let,const, классы - Зависят от контекста: Function Expressions
Вопрос 16. Почему сравнение объектов в JavaScript работает неочевидным образом?
Таймкод: 00:14:14
Ответ собеседника: Правильный. При присвоении c = a объекты равны по ссылке, при создании через {...a} — разные ссылки.
Правильный ответ: В JavaScript сравнение объектов имеет особенности, связанные с работой с ссылками и значениями:
1. Сравнение по ссылкам
-
Примитивы (числа, строки, булевы) сравниваются по значению:
const a = 5;
const b = 5;
console.log(a === b); // true -
Объекты (включая массивы, функции) сравниваются по ссылке:
const obj1 = { id: 1 };
const obj2 = { id: 1 };
console.log(obj1 === obj2); // false (разные объекты в памяти)
const arr1 = [1, 2];
const arr2 = [1, 2];
console.log(arr1 === arr2); // false
2. Примеры поведения Случай 1: Присваивание по ссылке
const a = { x: 10 };
const b = a; // Копирование ссылки
console.log(a === b); // true (та же ячейка памяти)
b.x = 20;
console.log(a.x); // 20 (изменения видны через оба идентификатора)
Случай 2: Поверхностное копирование
const c = { ...a }; // Новый объект с теми же свойствами
console.log(a === c); // false (разные объекты)
c.x = 30;
console.log(a.x); // 20 (оригинал не изменился)
3. Глубокое сравнение Проблема:
const user1 = {
name: 'John',
address: { city: 'Paris' }
};
const user2 = {
name: 'John',
address: { city: 'Paris' }
};
// Поверхностное сравнение
console.log(user1 === user2); // false
console.log(_.isEqual(user1, user2)); // true (lodash)
Решение:
-
Ручное сравнение (не рекомендуется для сложных структур):
function shallowEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => obj1[key] === obj2[key]);
} -
Использование библиотек:
// Lodash
import _ from 'lodash';
_.isEqual(obj1, obj2);
// JSON.stringify (с ограничениями)
JSON.stringify(obj1) === JSON.stringify(obj2);
4. Особые случаи
-
Массивы:
const arr = [1, 2];
const copy = arr.slice();
console.log(arr === copy); // false -
Функции:
const fn1 = () => {};
const fn2 = () => {};
console.log(fn1 === fn2); // false -
Примитивы-объекты:
const str1 = 'text';
const str2 = new String('text');
console.log(str1 === str2); // false (string vs object)
5. Работа с React Проблема перерендера:
const Component = () => {
const [user, setUser] = useState({ name: 'John' });
const updateUser = () => {
user.name = 'Mike';
setUser(user); // Не вызовет ререндер (та же ссылка)
};
// Правильно: создание нового объекта
const correctUpdate = () => {
setUser({ ...user, name: 'Mike' });
};
};
6. Как сравнивает движок
-
Алгоритм SameValue (для
Object.is):Object.is(NaN, NaN); // true
Object.is(0, -0); // false -
Строгое равенство (
===):- Примитивы: сравнивает значения
- Объекты: сравнивает ссылки
- Особые значения:
NaN !== NaN,-0 === 0
Рекомендации:
- Для простых объектов используйте поверхностное сравнение
- Для сложных структур — библиотечные методы (Lodash.isEqual)
- В React используйте иммутабельные обновления
- Избегайте мутаций исходных объектов
Вопрос 16. Почему сравнение объектов в JavaScript работает неочевидным образом?
Таймкод: 00:14:14
Ответ собеседника: Правильный. При присвоении c = a объекты равны по ссылке, при создании через {...a} — разные ссылки.
Правильный ответ: В JavaScript сравнение объектов имеет особенности, связанные с работой с ссылками и значениями:
1. Сравнение по ссылкам
-
Примитивы (числа, строки, булевы) сравниваются по значению:
const a = 5;
const b = 5;
console.log(a === b); // true -
Объекты (включая массивы, функции) сравниваются по ссылке:
const obj1 = { id: 1 };
const obj2 = { id: 1 };
console.log(obj1 === obj2); // false (разные объекты в памяти)
const arr1 = [1, 2];
const arr2 = [1, 2];
console.log(arr1 === arr2); // false
2. Примеры поведения Случай 1: Присваивание по ссылке
const a = { x: 10 };
const b = a; // Копирование ссылки
console.log(a === b); // true (та же ячейка памяти)
b.x = 20;
console.log(a.x); // 20 (изменения видны через оба идентификатора)
Случай 2: Поверхностное копирование
const c = { ...a }; // Новый объект с теми же свойствами
console.log(a === c); // false (разные объекты)
c.x = 30;
console.log(a.x); // 20 (оригинал не изменился)
3. Глубокое сравнение Проблема:
const user1 = {
name: 'John',
address: { city: 'Paris' }
};
const user2 = {
name: 'John',
address: { city: 'Paris' }
};
// Поверхностное сравнение
console.log(user1 === user2); // false
console.log(_.isEqual(user1, user2)); // true (lodash)
Решение:
-
Ручное сравнение (не рекомендуется для сложных структур):
function shallowEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => obj1[key] === obj2[key]);
} -
Использование библиотек:
// Lodash
import _ from 'lodash';
_.isEqual(obj1, obj2);
// JSON.stringify (с ограничениями)
JSON.stringify(obj1) === JSON.stringify(obj2);
4. Особые случаи
-
Массивы:
const arr = [1, 2];
const copy = arr.slice();
console.log(arr === copy); // false -
Функции:
const fn1 = () => {};
const fn2 = () => {};
console.log(fn1 === fn2); // false -
Примитивы-объекты:
const str1 = 'text';
const str2 = new String('text');
console.log(str1 === str2); // false (string vs object)
5. Работа с React Проблема перерендера:
const Component = () => {
const [user, setUser] = useState({ name: 'John' });
const updateUser = () => {
user.name = 'Mike';
setUser(user); // Не вызовет ререндер (та же ссылка)
};
// Правильно: создание нового объекта
const correctUpdate = () => {
setUser({ ...user, name: 'Mike' });
};
};
6. Как сравнивает движок
-
Алгоритм SameValue (для
Object.is):Object.is(NaN, NaN); // true
Object.is(0, -0); // false -
Строгое равенство (
===):- Примитивы: сравнивает значения
- Объекты: сравнивает ссылки
- Особые значения:
NaN !== NaN,-0 === 0
Рекомендации:
- Для простых объектов используйте поверхностное сравнение
- Для сложных структур — библиотечные методы (Lodash.isEqual)
- В React используйте иммутабельные обновления
- Избегайте мутаций исходных объектов
Вопрос 17. Чем отличаются apply, call и bind в JavaScript?
Таймкод: 00:15:00
Ответ собеседника: Правильный. call/apply сразу вызывают функцию с контекстом (разная передача аргументов), bind создает новую функцию с привязанным контекстом.
Правильный ответ: Эти методы позволяют управлять контекстом (this) и аргументами функции. Рассмотрим детали:
1. Function.prototype.call()
- Вызывает функцию немедленно
- Первый аргумент — контекст (
this) - Последующие аргументы — передаются функции как параметры через запятую
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'John' };
greet.call(user, 'Hello', '!'); // "Hello, John!"
2. Function.prototype.apply()
- Вызывает функцию немедленно
- Первый аргумент — контекст (
this) - Второй аргумент — массив (или array-like объект) аргументов
const args = ['Hi', '.'];
greet.apply(user, args); // "Hi, John."
Особый случай: Использование с Math.max
const numbers = [5, 6, 2, 3, 7];
Math.max.apply(null, numbers); // 7
3. Function.prototype.bind()
- Создаёт новую функцию с привязанным контекстом и аргументами
- Не вызывает функцию сразу
- Аргументы могут быть частично применены (каррирование)
const boundGreet = greet.bind(user, 'Hey');
boundGreet('...'); // "Hey, John..."
4. Сравнительная таблица
| Метод | Вызов | Аргументы | Возвращает |
|---|---|---|---|
call | Немедленно | Отдельные значения | Результат функции |
apply | Немедленно | Массив | Результат функции |
bind | Позже | Отдельные значения или массив | Новую связанную функцию |
5. Особенности стрелочных функций
Стрелочные функции не имеют своего this, поэтому методы call/apply/bind не могут изменить их контекст:
const arrowFunc = () => this.name;
arrowFunc.call({ name: 'John' }); // undefined (если глобальный name не определён)
6. Современные альтернативы
-
Spread оператор заменяет
apply:Math.max(...numbers); // Вместо apply -
Деструктуризация для частичного применения:
const greetJohn = (...args) => greet.call(user, ...args);
7. Полифил для bind
Пример реализации (упрощённый):
Function.prototype.myBind = function(context, ...args) {
const fn = this;
return function(...innerArgs) {
return fn.apply(context, [...args, ...innerArgs]);
};
};
8. Практические применения
-
Заимствование методов:
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(arrayLike, '-'); // 'a-b' -
Каррирование:
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2);
double(5); // 10 -
Сохранение контекста:
class Button {
constructor() {
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler() { /* ... */ }
}
Рекомендации:
- В современном коде используйте стрелочные функции для автоматического связывания
this - Для каррирования предпочитайте функции высшего порядка
- Используйте
bindтолько когда необходимо явное связывание контекста
Вопрос 17. Чем отличаются apply, call и bind в JavaScript?
Таймкод: 00:15:00
Ответ собеседника: Правильный. call/apply сразу вызывают функцию с контекстом (разная передача аргументов), bind создает новую функцию с привязанным контекстом.
Правильный ответ: Эти методы позволяют управлять контекстом (this) и аргументами функции. Рассмотрим детали:
1. Function.prototype.call()
- Вызывает функцию немедленно
- Первый аргумент — контекст (
this) - Последующие аргументы — передаются функции как параметры через запятую
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'John' };
greet.call(user, 'Hello', '!'); // "Hello, John!"
2. Function.prototype.apply()
- Вызывает функцию немедленно
- Первый аргумент — контекст (
this) - Второй аргумент — массив (или array-like объект) аргументов
const args = ['Hi', '.'];
greet.apply(user, args); // "Hi, John."
Особый случай: Использование с Math.max
const numbers = [5, 6, 2, 3, 7];
Math.max.apply(null, numbers); // 7
3. Function.prototype.bind()
- Создаёт новую функцию с привязанным контекстом и аргументами
- Не вызывает функцию сразу
- Аргументы могут быть частично применены (каррирование)
const boundGreet = greet.bind(user, 'Hey');
boundGreet('...'); // "Hey, John..."
4. Сравнительная таблица
| Метод | Вызов | Аргументы | Возвращает |
|---|---|---|---|
call | Немедленно | Отдельные значения | Результат функции |
apply | Немедленно | Массив | Результат функции |
bind | Позже | Отдельные значения или массив | Новую связанную функцию |
5. Особенности стрелочных функций
Стрелочные функции не имеют своего this, поэтому методы call/apply/bind не могут изменить их контекст:
const arrowFunc = () => this.name;
arrowFunc.call({ name: 'John' }); // undefined (если глобальный name не определён)
6. Современные альтернативы
-
Spread оператор заменяет
apply:Math.max(...numbers); // Вместо apply -
Деструктуризация для частичного применения:
const greetJohn = (...args) => greet.call(user, ...args);
7. Полифил для bind
Пример реализации (упрощённый):
Function.prototype.myBind = function(context, ...args) {
const fn = this;
return function(...innerArgs) {
return fn.apply(context, [...args, ...innerArgs]);
};
};
8. Практические применения
-
Заимствование методов:
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(arrayLike, '-'); // 'a-b' -
Каррирование:
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2);
double(5); // 10 -
Сохранение контекста:
class Button {
constructor() {
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler() { /* ... */ }
}
Рекомендации:
- В современном коде используйте стрелочные функции для автоматического связывания
this - Для каррирования предпочитайте функции высшего порядка
- Используйте
bindтолько когда необходимо явное связывание контекста
