РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ / 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.itemsPerWorkerif 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 LRA[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 := 0for {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.ymltest:stage: testimage: golang:1.21script:- 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 недели с анализом метрик:
-- Анализ скорости закрытия задачSELECTAVG(EXTRACT(EPOCH FROM (closed_at - created_at))) / 3600 AS avg_hoursFROM issuesWHERE 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.gofunc 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 LRA[Analysis] --> B[Tech Design]B --> C[Implementation]C --> D[Code Review]D --> E[QA]E --> F[Deploy] -
Code Review с акцентом на:
- Конкурентную безопасность (проверка data races через
-raceфлаг) - Эффективность SQL-запросов:
EXPLAIN ANALYZESELECT * FROM bookingsWHERE 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- deploygo_test:stage: testscript:- go vet ./...- staticcheck ./...- go test -race -covermode=atomic -coverprofile=coverage.out ./...docker_build:stage: buildonly:- merge_requestsscript:- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .deploy_staging:stage: deployenvironment: stagingscript:- 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 bookingrequestBody: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 reservationfix(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 Deploymentstrategy: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_timeFROM pg_stat_statementsORDER BY total_time DESCLIMIT 10;
- Ретроспективы с фокусом на улучшения:
- Внедрение DORA-метрик (Deployment Frequency, Lead Time)
- Автоматизация рутинных операций через скрипты Go
Рекомендация: Для позиций уровня Senior+ делайте акцент на архитектурных решениях, покажите глубокое понимание полного жизненного цикла задачи — от анализа до эксплуатации. Упоминание конкретных инструментов и метрик повышает доверие к опыту.
Вопрос 6. Присутствовали ли в команде дизайнер и бизнес-аналитик?
Таймкод: 00:04:12
Ответ собеседника: Неполный. Дизайнера не было, работали по готовым макетам (источник макетов не указан). Аналитики в команде не было.
Правильный ответ: Отсутствие этих ролей компенсировалось следующими практиками:
1. Работа с дизайном
- Источник макетов: Использовали шаблоны Material UI с кастомизацией под бренд. Пример структуры компонентов:
// React-компонент для выбора времени<TimePickerampm={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-аналитика:
-- Поиск узких мест в бронированииSELECTEXTRACT(HOUR FROM created_at) AS hour,COUNT(*) FILTER (WHERE status = 'success') AS success,COUNT(*) FILTER (WHERE status = 'failed') AS failedFROM bookingsGROUP BY 1ORDER BY failed DESCLIMIT 5;
- Go-сервис для сбора метрик:
func (t *Tracker) TrackEvent(ctx context.Context, event Event) error {// Асинхронная запись в Kafkago 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 BookingRepositorypaymentClient PaymentGatewaynotifier 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 testpact.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: buildscript:- 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 LRA[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 видов)
- Особенности:
- Иммутабельность (при "изменении" создается новая копия)
- Передача по значению
- Не имеют методов (автоупаковка в объекты при вызове методов)
- Полный список:
// Проверка через typeoftypeof 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); // truefunc instanceof Function; // true
3. Специальные случаи
-
Отличие
nullотundefined:undefined: переменная объявлена, но значение не присвоеноnull: явное указание на отсутствие значения
let a; // undefinedlet b = null; // nulltypeof 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); // 1Number(''); // 0Boolean([]); // 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); // NaNNumber(null); // 0Boolean(undefined); // falseBoolean(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 | undefinedfunction 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); // NaNNumber(null); // 0Boolean(undefined); // falseBoolean(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 | undefinedfunction 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); // ReferenceErrorconsole.log(z); // ReferenceError
2. Поднятие (Hoisting)
-
var— инициализируется какundefinedдо объявления:console.log(a); // undefinedvar a = 5; -
let/const— тоже поднимаются, но остаются в "временной мёртвой зоне" (TDZ):console.log(b); // ReferenceErrorlet b = 10;
3. Повторное объявление
-
var— позволяет переопределять в той же области:var c = 1;var c = 2; // ОК -
let/const— запрещают повторное объявление:let d = 3;let d = 4; // SyntaxErrorconst e = 5;const e = 6; // SyntaxError
4. Иммутабельность
-
const— защищает от переприсваивания, но не от мутаций:const obj = { name: 'John' };obj.name = 'Mike'; // ОКobj = {}; // TypeErrorconst 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); // undefinedvar x = 10; - Эквивалентно:
var x; // Поднятоconsole.log(x); // undefinedx = 10;
3. let и const Variables
- Технически поднимаются, но попадают во временную мёртвую зону (TDZ):
console.log(y); // ReferenceErrorlet y = 20;console.log(z); // ReferenceErrorconst z = 30;
- Доступны только после объявления в блоке.
4. Function Expressions (Функциональные выражения)
- Зависят от типа переменной:
// С varconsole.log(funcVar); // undefinedfuncVar(); // TypeError: funcVar is not a functionvar funcVar = function() {};// С let/constfuncLet(); // ReferenceErrorlet funcLet = () => {};
5. Классы (Classes)
- Не поднимаются (аналогично
let/const):const obj = new MyClass(); // ReferenceErrorclass 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]);} -
Использование библиотек:
// Lodashimport _ 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); // trueObject.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]);} -
Использование библиотек:
// Lodashimport _ 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); // trueObject.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только когда необходимо явное связывание контекста
