Как стать автором
Обновить
182.99

Middle JavaScript: как избежать ловушек hoisting'а, объектов и связных списков на собеседованиях

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров1.7K

Привет, Хабр! После прошлого поста делюсь новым разбором задач с собеседований. Сегодня разберём три ключевые темы: поднятие (hoisting), работу с объектами и реализацию связного списка. Погнали!Для кого эти задачи и что проверяют?
Эти вопросы часто встречаются на собеседованиях для Middle JavaScript-разработчиков. Через них проверяют:

➕ Понимание «подводных камней» языка (hoisting, TDZ, ссылочные типы);
➕ Умение работать с низкоуровневыми структурами данных;
➕ Способность предвидеть edge-кейсы.


▍ Часть 1: Области видимости и hoisting: почему это до сих пор спрашивают?


Каждый день разработчик объявляет множество переменных, и кажется, что здесь нет ничего сложного. Но JavaScript хранит в себе множество нюансов, которые превращают простое объявление let/const или var в ловушку для разработчиков.

Что проверяют работодатели:

  • Понимание временной мёртвой зоны (TDZ): Знаете ли вы, почему обращение к переменной до её объявления иногда вызывает ошибку, а иногда — нет?

  • Чувство области видимости: Можете ли вы предсказать, где переменная «живёт» — внутри блока, функции или глобально?

  • Осознанный выбор инструментов: Зачем в 2025 году спрашивать про устаревший var? Ответ прост: чтобы проверить, понимаете ли вы эволюцию языка и причины появления let/const.

Главный подвох задач на hoisting — иллюзия доступности. Переменные var создают «призраков»: они существуют в коде до объявления, но их значение — undefined. С let/const всё строже: попытка использовать их до инициализации ломает код, что часто становится сюрпризом для тех, кто привык к другим языкам.

Практическая ценность:

Эти нюансы критичны не только для собеседований. Например, ошибки TDZ возникают при работе с асинхронными операциями, когда переменная объявляется позже вызова. Понимание областей видимости помогает избежать багов в больших проектах, где одни и те же имена переменных могут использоваться в разных модулях.

Функция 1: TDZ в действии

function doSome() {
    if (true) {
        console.log(x, y); // 🚨 Ошибка!
    }

    let x = 2;
    var y = 3;

    console.log(x, y); // Этот код не выполнится

}

Что происходит:

  • let x:

    • Объявлена после блока if, но попытка чтения происходит до объявления.

    • Из-за TDZ обращение к x до инициализации вызовет ошибку: ReferenceError: Cannot access 'x' before initialization.

  • var y:

    • Поднимается в начало функции со значением undefined, поэтому доступна (но выведет undefined без ошибки).

Итог: Код упадёт на первом console.log, так как x находится в TDZ.


Функция 2: Блоки и повторные объявления

function doSome() {
    if (true) {
        let x = 2;
        var y = 3;

        console.log(x, y); // ✅ 2 3

    }

    console.log(x, y); // 🚨 Ошибка!

    let x = 2; // Переобъявление x
    var y = 3; // Перезапись y
}

Разбор:

  1. Внутри if:

    • let x видна только внутри блока.

    • var y поднимается в начало функции, присваивается 3.

    • Вывод: 2 3.

  2. Вне if:

    • console.log(x): x из блока if уже не существует. Попытка обратиться к новой x, объявленной через let ниже, снова приводит к TDZ → ReferenceError.

    • console.log(y): y уже равен 3 благодаря hoisting.

Итог: Первый вывод корректен, второй вызовет ошибку из-за TDZ для x.


▍ Часть 2: Объекты в JavaScript: почему копирование — это не всегда просто?

Работа с объектами кажется интуитивно понятной, пока вы не столкнётесь с их «ссылочной» природой. Задачи на объекты проверяют не только знание синтаксиса, но и понимание того, как данные хранятся в памяти.

Что проверяют работодатели:

  • Работу с ссылочными типами: Понимаете ли вы, что присваивание объекта создаёт ссылку, а не копию?

  • Особенности оператора ... (spread): Знаете ли вы, что он делает поверхностное копирование, оставляя вложенные объекты связанными?

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

Главный подвох задач на объекты — иллюзия независимости. Например, два объекта {a: 1} и {a: 1} не равны друг другу, а изменение свойства в «скопированном» объекте может затронуть исходный. Это ловушка для тех, кто не осознаёт разницу между поверхностным и глубоким копированием.

Практическая ценность:

  • Ошибки с ссылками часто возникают при работе с состоянием в React/Vue, где неверное копирование приводит к лишним ререндерам или багам.

  • Понимание ключей-объектов помогает при работе с Map и WeakMap, где идентичность сохраняется.

Совет: Всегда уточняйте, требуется ли глубокая копия. Используйте structuredClone() или иммутабельные подходы, чтобы избежать неожиданных мутаций.

Даны два объекта:

const a = {
    set: { foo: { bar: 10 } }, // 🧐 Ключи set и delete — разрешены?
    delete: { foo: { bar: 20 } },
};

const b = {
    set: { foo: { bar: 10 } },
    delete: { foo: { bar: 20 } },
};

Вопрос: Можно ли использовать set и delete как ключи?
Ответ: Да! Эти слова зарезервированы для операторов, но в объектах их можно использовать как ключи.


Эксперименты с объектами:

1. Слияние через spread:

const sum = { ...a, ...b };

Поверхностное копирование: sum.set и sum.delete берутся из b (последний в spread имеет приоритет).

Вложенные объекты остаются ссылками на оригиналы из b.

2. Изменение вложенного свойства:

sum.set.foo.bar = 11;

Изменит b.set.foo.bar и sum.set.foo.bar, так как они ссылаются на один объект.

3. Полная перезапись:

sum.set = { foo: { bar: 13 } };

sum.set больше не связан с b.set, следовательно меняется только sum.set, b.set остается прежним.

4. Ключи-объекты:

sum[a] = 14; // sum["[object Object]"] = 14

sum[b] = 15; // sum["[object Object]"] = 15 (перезапись)

Ключи-объекты преобразуются в строку [object Object], поэтому значения перезаписываются.


▍ Часть 3: Связные списки: зачем их реализовывать вручную?

Связные списки — базовая структура данных, которая редко используется напрямую в веб-разработке. Однако задачи на их реализацию раскрывают навык работы с низкоуровневыми концепциями.

Что проверяют работодатели:

  • Умение оперировать ссылками: Можете ли вы управлять связями между узлами без ошибок?

  • Обработку edge-кейсов: Помните ли вы про пустые списки, обновление head и tail?

  • Понимание Big O: Сможете ли вы объяснить, когда список эффективнее массива

Главный подвох задач на списки — незаметные ошибки в логике связей. Например, если забыть обновить tail при добавлении элемента, список превратится в «битый» набор узлов. Это проверяет внимательность к деталям.

Практическая ценность:

  • Связные списки лежат в основе LRU-кэшей, очередей задач и систем типа «Отменить/Повторить».

  • Понимание их устройства помогает оптимизировать вложенные структуры данных, где вставка/удаление должны быть быстрыми.

Совет: Даже если не пишете списки ежедневно, разберитесь в их устройстве. Это основа для изучения более сложных структур — деревьев, графов и хэш-таблиц.

Задача: Создать класс LinkedList с методами:

  • push(value) — добавление в конец.

  • toArray() — преобразование в массив.

Решение:

class LinkedList {
    constructor() {
        this.head = null; // Первый элемент
        this.tail = null; // Последний элемент
    }

    push(value) {
        const newNode = { value, next: null };
        if (!this.head || !this.tail) { // Если список пуст
            this.head = newNode;
            this.tail = newNode;

            return this; // Для цепочки вызовов
        }

        // Добавление в конец
        this.tail.next = newNode;
        this.tail = newNode;

        return this; // Для цепочки вызовов

    }

    toArray() {
        const arr = [];
        let current = this.head;

        while (current) { // Идём от head к tail
            arr.push(current.value);
            current = current.next;
        }

        return arr;

    }

}

Пример использования:

const list = new LinkedList();
list.push(1).push(2).push(3);
console.log(list.toArray()); // [1, 2, 3]

Как это работает:

  • Каждый узел содержит value и ссылку next.

  • push обновляет tail, toArray проходит от head до tail, собирая значения.


P.S. Если хотите глубже погрузиться в тему, изучите двусвязные списки, я привел лишь базовый пример, который попался мне. Удачи на собеседованиях!

Теги:
Хабы:
+12
Комментарии3

Полезные ссылки

Как создавать сервис, когда ты со всех сторон ограничен законами

Время на прочтение7 мин
Количество просмотров2.1K
Всего голосов 14: ↑13 и ↓1+14
Комментарии1

Парадокс профессии UX-писателя

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров1.9K
Всего голосов 10: ↑10 и ↓0+10
Комментарии0

Что скрывается за вводом пароля: как мы обновили сервис авторизации в Битрикс24

Время на прочтение5 мин
Количество просмотров2.4K
Всего голосов 18: ↑12 и ↓6+9
Комментарии9

Информация

Сайт
www.bitrix24.ru
Дата регистрации
Дата основания
1998
Численность
501–1 000 человек
Местоположение
Россия