Вопросы и задачи на собеседованиях 113

Типы данных в JavaScript?

В JavaScript существует несколько типов данных, которые можно разделить на примитивные и объектные типы.

Примитивные типы данных:

  1. string (строка)

    • Представляет текстовые данные.
    • Строки заключаются в одинарные ('...'), двойные ("..."), или косые кавычки (template literals: `...`).
    • Пример:
      let name = "Alice";
  2. number (число)

    • Представляет как целые числа, так и числа с плавающей точкой.
    • Специальные значения: NaN (не число) и Infinity.
    • Пример:
      let age = 25;
      let price = 19.99;
  3. boolean (логический тип)

    • Имеет два значения: true и false.
    • Пример:
      let isActive = true;
      let isLoggedIn = false;
  4. null (нулевое значение)

    • Представляет "ничего" или "пустое" значение.
    • Это явное указание на отсутствие значения.
    • Пример:
      let user = null;
  5. undefined (неопределённое значение)

    • Означает, что переменная была объявлена, но ей не было присвоено значения.
    • Пример:
      let result;
      console.log(result); // undefined
  6. symbol (символ)

    • Представляет уникальные и неизменяемые значения, часто используемые для идентификации объектов.
    • Пример:
      let uniqueId = Symbol('id');
  7. bigint (большое целое число)

    • Позволяет работать с числами больше, чем поддерживает тип number (больше чем 2^53 - 1).
    • Пример:
      let largeNumber = 1234567890123456789012345678901234567890n;

Объектный тип:

  1. object (объект)
    • Это комплексный тип данных, который используется для хранения коллекций данных и более сложных сущностей.
    • Объекты создаются с помощью фигурных скобок {} и могут содержать пары "ключ-значение".
    • Пример:
      let person = {
       name: "Alice",
       age: 25
      };

Примечания:

  • Примитивные типы данных являются неизменяемыми (immutable), то есть их значения нельзя изменить после создания.
  • Объекты, напротив, являются изменяемыми (mutable), их свойства можно менять после создания.

Проверка типа данных:

Для того чтобы узнать тип данных переменной, используется оператор typeof.

Пример:

let value = 42;
console.log(typeof value); // "number"

Разница между == и === (нестрогое/строгое равенство)?

В JavaScript есть два оператора для сравнения: нестрогое равенство (==) и строгое равенство (===). Они отличаются тем, как обрабатывают типы данных при сравнении.

1. Нестрогое равенство (==)

  • Оператор == преобразует типы данных перед сравнением. Это значит, что если два значения имеют разные типы, JavaScript попытается привести одно или оба значения к общему типу.
  • Этот процесс называется приведением типов (type coercion).

Пример:

5 == '5';   // true (строка '5' преобразуется в число 5)
true == 1;  // true (логическое true преобразуется в число 1)
null == undefined;  // true (оба считаются равными при нестрогом сравнении)

2. Строгое равенство (===)

  • Оператор === не преобразует типы данных. Он сравнивает как значения, так и их типы. Если типы не совпадают, результатом сравнения будет false.

Пример:

5 === '5';  // false (разные типы: число и строка)
true === 1; // false (разные типы: boolean и number)
null === undefined; // false (разные типы)
Операция == (нестрогое равенство) === (строгое равенство)
5 == '5' true false
0 == false true false
null == undefined true false
NaN == NaN false false
[] == false true false
'' == 0 true false
true == 1 true false
[1] == 1 true false

Что такое Strict mode в JavaScript?

Strict Mode в JavaScript — это специальный режим, который делает код более строгим и безопасным. Он вводит более строгие правила для обработки ошибок и помогает избежать потенциальных проблем. Этот режим был введён в ECMAScript 5 (ES5) и может применяться как ко всему скрипту, так и к отдельной функции.

Как включить Strict Mode?

Для активации строгого режима нужно добавить директиву:

// Строгий режим для всего скрипта
"use strict";

function test() {
  // Строгий режим только внутри функции
  "use strict";
}

Основные особенности Strict Mode

  1. Запрещает использование неявно объявленных переменных:

    • В строгом режиме переменные должны быть объявлены через var, let, или const.
      "use strict";
      x = 10; // Ошибка: x is not defined
  2. Запрещает дублирование имен параметров в функциях:

    • Нельзя использовать одинаковые имена для параметров функции.
      "use strict";
      function sum(a, a) { // Ошибка: дублирование имени параметра
      return a + a;
      }
  3. Запрещает удаление переменных и функций:

    • Оператор delete нельзя использовать для удаления переменных, функций или аргументов.
      "use strict";
      var x = 5;
      delete x; // Ошибка: Нельзя удалить переменную
      }
  4. this в глобальном контексте:

    • В строгом режиме, если функция вызывается в глобальном контексте, значение this внутри неё будет undefined, а не объект глобального контекста (например, window в браузере).
      "use strict";
      function f() {
      console.log(this); // undefined
      }
      f();
  5. Запрещены зарезервированные слова:

    • Нельзя использовать зарезервированные слова в качестве идентификаторов, например: implements, interface, package, private, и другие.

Модули и классы в ES6+ по умолчанию работают в строгом режиме. Тем не менее, использование строгого режима остаётся полезным в других контекстах.

Типы таймеров в JavaScript?

В JavaScript существует несколько основных типов таймеров, которые позволяют выполнять код через определенные интервалы времени или с задержкой. Вот основные:

1. setTimeout(callback, delay)

Запускает функцию или код один раз через заданную задержку (в миллисекундах).

  • Пример:
    setTimeout(() => {
    console.log("Прошло 2 секунды");
    }, 2000);

2. setInterval(callback, delay)

Запускает функцию или код через регулярные интервалы времени, повторяя её каждые delay миллисекунд.

  • Пример:

    const intervalId = setInterval(() => {
    console.log("Каждые 2 секунды");
    }, 2000);
  • Чтобы остановить таймер, используется clearInterval(intervalId):

    clearInterval(intervalId);

3. setImmediate(callback) (в среде Node.js)

Запускает функцию сразу после завершения текущей операции. В браузере эта функция не поддерживается.

  • Пример (в Node.js):
    setImmediate(() => {
    console.log("Этот код выполнится сразу после текущей операции");
    });

4. requestAnimationFrame(callback) (для анимаций)

Запускает функцию перед следующим перерисовыванием страницы, что полезно для создания плавных анимаций.

  • Пример:

    function animate() {
    console.log("Анимация кадра");
    requestAnimationFrame(animate);
    }
    
    requestAnimationFrame(animate);
  • Чтобы остановить анимацию, используется cancelAnimationFrame(id):

    const animationId = requestAnimationFrame(animate);
    cancelAnimationFrame(animationId);

5. clearTimeout(timeoutId) и clearInterval(intervalId)

Останавливают таймеры, запущенные с помощью setTimeout и setInterval, соответственно.

  • Пример:

    const timeoutId = setTimeout(() => {
    console.log("Эта функция не выполнится, так как таймер будет очищен");
    }, 5000);
    
    clearTimeout(timeoutId);

Эти таймеры используются для управления временем выполнения кода в JavaScript, будь то однократная задержка, регулярные интервалы или анимации.

Что такое поднятие (Hoisting)?

Hoisting — это механизм в JavaScript, при котором объявления переменных и функций перемещаются в верхнюю часть их области видимости во время компиляции, еще до выполнения кода.

Поднятие переменных

Для переменных, объявленных с использованием var, их объявление поднимается в начало области видимости, но их инициализация не поднимается. Это означает, что к переменной можно обратиться до её фактического объявления, но значение будет undefined до тех пор, пока не выполнится инициализация.

console.log(a); // undefined
var a = 10;

В данном случае JavaScript видит это так:

var a;
console.log(a); // undefined
a = 10;

Однако переменные, объявленные с помощью let и const, поднимаются, но остаются в "временной мертвой зоне", пока выполнение не достигнет их инициализации.

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;

Поднятие функций

Функции, объявленные с помощью function declaration, поднимаются полностью (как их объявление, так и тело функции), что позволяет вызывать их до того, как они объявлены в коде.

foo(); // "Hello"
function foo() {
  console.log("Hello");
}

JavaScript видит это так:

function foo() {
  console.log("Hello");
}
foo(); // "Hello"

Функции, объявленные через function expression, поднимаются так же, как и переменные, — только объявление поднимается, но не инициализация.

bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("Hi");
};

Hoisting — это поведение JavaScript, о котором важно помнить при объявлении переменных и функций. Оно помогает избежать ошибок и правильно понимать, как интерпретатор JavaScript работает с кодом.

Разница между .call(), .apply() и .bind()?

В JavaScript методы .call(), .apply() и .bind() используются для управления контекстом выполнения функций, т.е. для указания, каким объектом будет this внутри функции.

1. .call()

Метод .call() вызывает функцию с явным указанием контекста this и аргументами, переданными по отдельности.

function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

greet.call(person, 'Hello', '!'); // Output: "Hello, Alice!"
  • Аргументы передаются через запятую.

2. .apply()

Метод .apply() схож с .call(), но аргументы передаются в виде массива.

greet.apply(person, ['Hi', '...']); // Output: "Hi, Alice..."
  • Аргументы передаются в массиве.

3. .bind()

Метод .bind() не вызывает функцию немедленно, а возвращает новую функцию с указанным контекстом this, которая может быть вызвана позже.

const greetPerson = greet.bind(person, 'Hey', '.');
greetPerson(); // Output: "Hey, Alice."
  • Возвращает новую функцию, связанную с переданным контекстом this.

  • Позволяет передавать частичные аргументы (каррирование).

  • .call() — вызывает функцию с контекстом и аргументами, переданными по отдельности.

  • .apply() — вызывает функцию с контекстом и аргументами в виде массива.

  • .bind() — возвращает новую функцию с указанным контекстом, но не вызывает её сразу.

Что такое IIFE?

IIFE (Immediately Invoked Function Expression) — это функция в JavaScript, которая создаётся и вызывается немедленно. Она используется для изоляции переменных и создания локальной области видимости, предотвращая "засорение" глобальной области видимости.

Пример IIFE:

(function() {
  console.log("Это IIFE!");
})();

Объяснение:

  1. Функциональное выражение: (function() { ... }) — это анонимная функция, заключённая в скобки, чтобы сделать её выражением.
  2. Немедленный вызов: () — эти скобки сразу вызывают функцию.

Почему используется IIFE:

  1. Изоляция переменных: Переменные внутри IIFE не попадают в глобальную область видимости, что помогает избежать конфликтов имён переменных.
  2. Модульный код: Часто используется для создания модулей, которые работают независимо друг от друга.
  3. Закрытие (closure): IIFE создаёт локальную область видимости, и переменные внутри неё могут быть замкнуты и доступны только в пределах этой функции.

Пример с переменными:

(function() {
  let message = "Привет, мир!";
  console.log(message);  // "Привет, мир!"
})();

// console.log(message);  // Ошибка! message не доступна вне IIFE

В этом примере переменная message доступна только внутри IIFE и не может быть использована снаружи.

Что такое NaN? Как определить, что значение равно NaN?

NaN (Not-a-Number) — это специальное значение в программировании, которое используется для представления чисел, результат которых неопределен или не может быть выражен в числовом формате. Например, деление 0 на 0 или результат корня из отрицательного числа.

Как определить, что значение равно NaN?

В JavaScript есть специальная функция для проверки, является ли значение NaN:

isNaN(value)

Однако есть важный момент: NaN не равен самому себе, что делает его уникальным среди значений. Чтобы точно проверить, является ли значение NaN, рекомендуется использовать:

Number.isNaN(value)

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

Пример:

console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true

Источники NaN

Значение NaN может появиться в нескольких случаях, например:

  • Результат недопустимых математических операций, таких как деление 0 на 0
  • Преобразование нечислового значения в число, например, при использовании функции parseInt на строке, которая не содержит чисел