Перейти к основному содержимому

РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ / Middle FRONTEND разработчик ЧистоАПП -

· 42 мин. чтения

Вопрос 1. Расскажите о своём профессиональном опыте и образовании.

Таймкод: 00:01:27

Ответ собеседника: Правильный. Студент 2 курса РТУ МИРЭА, начал карьеру с вёрстки в 16 лет, затем фриланс (HTML/CSS/JS), освоил React, работал в компании Lucky Laky фронтенд-разработчиком.

Правильный ответ: При презентации профессионального опыта на позицию разработчика рекомендуется структурировать ответ по ключевым блокам:

  1. Текущая роль и фокус
    Пример: «Специализируюсь на backend-разработке на Go с акцентом на высоконагруженные системы. В последние 2 года активно работаю с распределёнными системами и микросервисной архитектурой».

  2. Образование и ранний опыт
    Упомяните значимые курсы или проекты. Для студента:
    «Изучаю Computer Science в РТУ МИРЭА, параллельно развиваюсь в промышленной разработке. Первый коммерческий опыт получил в 16 лет, создавая адаптивную вёрстку для малого бизнеса».

  3. Ключевые этапы карьеры
    Детализируйте технологии и достижения:
    «На фрилансе реализовал 15+ SPA-приложений на React/TypeScript. В Lucky Laky участвовал в рефакторинге legacy-кода, что сократило время загрузки интерфейса на 40%. Переход на Go начал с разработки CLI-утилиты для внутреннего тестирования API».

  4. Технический стек
    Сгруппируйте навыки по уровням:

    - **Языки**: Go (2 года), TypeScript (3 года)
    - **Базы данных**: PostgreSQL (оптимизация запросов, EXPLAIN ANALYZE), Redis
    - **Инфраструктура**: Docker, Kubernetes, GitLab CI/CD
    - **Паттерны**: Clean Architecture, CQRS, Event Sourcing
  5. Пет-проекты (для 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».

  6. Карьерные цели
    «Сейчас стремлюсь углубиться в проектирование высокодоступных систем. Интересуюсь решениями вроде распределённых транзакций через 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. Вот детализация процессов:

  1. Роли и зоны ответственности
  • Тимлид:

    • Проводил ежедневные 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
  1. Процессы разработки
  • Гит-стратегия: 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)
  • Деплой: Blue-Green деплойменты в Kubernetes с feature flags
  1. Метрики эффективности
  • Lead Time: Сократили с 5 дней до 8 часов через:
    • Параллелизацию тестов в CI
    • Автоматизацию миграций БД (Goose)
  • Инциденты: Менее 2% отказов деплоев благодаря:
    • Canary-релизам
    • Integration-тестам в продакшн-подобном окружении (testcontainers)
  1. Инструментарий
graph TD
A[Jira] --> B[GitLab]
B --> C[CI Pipeline]
C --> D[Registry]
D --> E[Kubernetes]
E --> F[Prometheus/Grafana]
  1. Коммуникация
  • Ежедневные стендапы с фокусом на проблемах, а не статусах
  • Ретроспективы раз в 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

Ответ собеседника: Неполный. Двухнедельные спринты с последующей проверкой тимлидом, доработки при необходимости. Не упомянуты процессы аналитики и проектирования.

Правильный ответ: Полный цикл разработки включал следующие этапы:

  1. Подготовка и проектирование
  • Глубинный анализ требований:
    Проводились сессии 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
      }
  1. Процесс разработки
  • Спринты по 2 недели с чётким определением Definition of Done:

    1. Код покрыт юнит-тестами (минимум 80% по ключевым пакетам)
    2. Интеграционные тесты для основных сценариев
    3. Документация Swagger обновлена
    4. Проведён нагрузочный тест для высоконагруженных эндпоинтов
  • Пример 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)
  1. Автоматизация
  • 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
  1. Контроль качества
  • Статический анализ:
    • 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)
      }
      })
      }
  • Нагрузочное тестирование с использованием wrk2:
    wrk2 -t4 -c100 -d60s -R1000 http://localhost:8080/api/bookings
  1. Пост-релизные активности
  • Мониторинг через 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. Коммуникационные практики

  • Трехэтапные уточнения для сложных задач:
    1. Обсуждение с PO: бизнес-цель и KPI
    2. Технический брифинг: оценки рисков
    3. Дизайн-ревью: валидация архитектуры
  • Пример согласования фичи:
    ## Фича: Отмена бронирования

    Бизнес-требование: Пользователи могут отменять бронь за 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()
  • Миграции:

    • 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 для распределенной трассировки
  • CI/CD:

    • GitLab CI с многоступенчатой сборкой:
      build:
      stage: build
      script:
      - CGO_ENABLED=0 GOOS=linux go build -o app .
      artifacts:
      paths:
      - app

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 для поиска узких мест

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)
      }
      }
      }

Рекомендация: Для бэкенд-ролей структурируйте стек по категориям (базы данных, инфраструктура, безопасность), акцентируя темы, релевантные вакансии. Упоминайте конкретные версии (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: указатель на нулевой адрес
  • Пример с памятью:

    // Более эффективно при итерациях
    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: указатель на нулевой адрес
  • Пример с памятью:

    // Более эффективно при итерациях
    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. Три фазы распространения

  1. Capture Phase (Фаза захвата)
    Событие движется от корня документа к целевому элементу:
    Window → Document → <html> → <body> → ... → Целевой элемент
  2. Target Phase (Целевая фаза)
    Событие достигло целевого элемента (event.target).
  3. 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 / blur
  • load / unload
  • mouseenter / 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. Фазы жизненного цикла события

  1. Capture Phase (Фаза захвата)
    Событие движется сверху вниз от корневого элемента (window) до целевого элемента:

    window → document → <html> → <body> → родительские элементы → целевой элемент
    • Используется редко, но критична для перехвата событий до их обработки
    element.addEventListener('click', handler, { capture: true });
  2. Target Phase (Целевая фаза)
    Событие достигло элемента, на котором произошло действие (event.target):

    • Все обработчики на целевом элементе выполняются в порядке добавления
    button.addEventListener('click', () => {
    console.log('Target handler 1');
    });
    button.addEventListener('click', () => {
    console.log('Target handler 2');
    });
  3. Bubble Phase (Фаза всплытия)
    Событие поднимается обратно к корню документа:

    целевой элемент → родительские элементы → <body> → <html> → document → window
    parent.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/unload
  • mouseenter/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. Детализация фаз

  1. Capture Phase (Фаза захвата / погружение)

    • Событие движется сверху вниз от window до целевого элемента
    • Порядок: window → document → <html> → <body> → родительские элементы → event.target
    // Обработчик на фазе захвата
    parent.addEventListener('click', handler, { capture: true });
  2. Target Phase (Фаза цели)

    • Событие достигло элемента, на котором произошло действие (event.target)
    • Все обработчики на целевом элементе выполняются в порядке их добавления
    button.addEventListener('click', () => console.log('Handler 1'));
    button.addEventListener('click', () => console.log('Handler 2'));
  3. 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() работает как в DOM
  • e.stopPropagation() останавливает распространение только в React-дереве

Vue:

  • Модификаторы .prevent и .stop в директиве v-on:
    <form @submit.prevent="handleSubmit">
    <div @click.stop="handleClick"></div>

7. Рекомендации по использованию

  1. Избегайте глобального stopPropagation()
    Это может нарушить работу аналитики и других обработчиков.

  2. Проверяйте defaultPrevented
    В обработчиках верхнего уровня:

    document.addEventListener('click', (e) => {
    if (e.defaultPrevented) return;
    // Логика для необработанных событий
    });
  3. Для кастомных элементов используйте 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. Рекомендации по использованию

  1. Всегда используйте const по умолчанию, если переменная не будет переприсвоена.
  2. Используйте let только когда значение должно изменяться.
  3. Избегайте var в современном коде (кроме особых случаев).
  4. Для объектов/массивов:
    • Используйте const для ссылки
    • Для иммутабельности применяйте копирование или библиотеки типа Immutable.js

8. Сравнительная таблица

Особенностьvarletconst
Область видимостиФункцияБлокБлок
ПоднятиеДа (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. Приоритеты при поднятии Порядок приоритета (от высшего):

  1. Function Declarations
  2. var Variables
  3. Аргументы функции

Пример конфликта:

console.log(typeof greet); // "function"

var greet = "Hello";

function greet() {
return "Hi!";
}

console.log(typeof greet); // "string"

8. Лучшие практики

  1. Всегда объявляйте переменные до использования
    Избегайте зависимости от hoisting:

    // Плохо
    console.log(count);
    var count = 10;

    // Хорошо
    let count = 10;
    console.log(count);
  2. Используйте const/let вместо var
    Чтобы избежать TDZ и проблем с областью видимости.

  3. Размещайте функции в коде перед вызовами
    Даже с учётом hoisting'а — для читаемости.

  4. Для экспрессивного кода используйте 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)

Решение:

  1. Ручное сравнение (не рекомендуется для сложных структур):

    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]);
    }
  2. Использование библиотек:

    // 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)

Решение:

  1. Ручное сравнение (не рекомендуется для сложных структур):

    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]);
    }
  2. Использование библиотек:

    // 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. Практические применения

  1. Заимствование методов:

    const arrayLike = { 0: 'a', 1: 'b', length: 2 };
    Array.prototype.join.call(arrayLike, '-'); // 'a-b'
  2. Каррирование:

    function multiply(a, b) { return a * b; }
    const double = multiply.bind(null, 2);
    double(5); // 10
  3. Сохранение контекста:

    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. Практические применения

  1. Заимствование методов:

    const arrayLike = { 0: 'a', 1: 'b', length: 2 };
    Array.prototype.join.call(arrayLike, '-'); // 'a-b'
  2. Каррирование:

    function multiply(a, b) { return a * b; }
    const double = multiply.bind(null, 2);
    double(5); // 10
  3. Сохранение контекста:

    class Button {
    constructor() {
    this.clickHandler = this.clickHandler.bind(this);
    }

    clickHandler() { /* ... */ }
    }

Рекомендации:

  • В современном коде используйте стрелочные функции для автоматического связывания this
  • Для каррирования предпочитайте функции высшего порядка
  • Используйте bind только когда необходимо явное связывание контекста