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

JavaScript

Типы данных:

Примитивные:

String - строка Number - число Boolean - true/false (логический тип) BigInt - большое число Undefined - значение не установлено Null - отсутствие значения Symbol - представляет уникальное значение, которое часто применяется для обращения к свойствам сложных объектов

Ссылочные:

Object - объект

ООП

Основные принципы объектно-ориентированного программирования (ООП) в JavaScript

Классы и объекты: Классы представляют собой шаблоны, описывающие атрибуты (поля) и методы (функции) объектов. Объекты являются конкретными экземплярами классов.

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

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

Полиморфизм: Полиморфизм позволяет объектам с одинаковым интерфейсом вести себя по-разному в зависимости от их типа или контекста использования.

Абстракция: Абстракция позволяет скрыть сложность реализации объектов за простым интерфейсом. Это делает код более понятным и удобным для использования.

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

Классы и функциональное разделение:

Когда речь идет о структурировании кода, часто выделяют два основных подхода: классы и функциональное разделение.

  1. Классы:

    • Описание: Классы представляют собой основной инструмент объектно-ориентированного программирования (ООП). Они объединяют данные и методы, работающие с этими данными, внутри одной структуры.
    • Пример:
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}

start() {
console.log(`Starting ${this.make} ${this.model}`);
}
}

const myCar = new Car('Toyota', 'Camry');
myCar.start();
  • Функциональное разделение:
    • Описание: Функциональное разделение означает разделение кода на функции, которые выполняют конкретные задачи. Это структура кода, основанная на функциях, а не на классах. Обычно используется в функциональном программировании, но также может быть применено в JavaScript.
    • Пример:
function createCar(make, model) {
return {
make,
model,
start() {
console.log(`Starting ${make} ${model}`);
}
};
}

const myCar = createCar('Toyota', 'Camry');
myCar.start();

Выбор между классами и функциональным разделением зависит от контекста и предпочтений. В JavaScript, который поддерживает и ООП, и функциональный стиль, вы можете использовать оба подхода в зависимости от требований проекта и предпочтений команды разработчиков.

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

  • Используйте классы, если ваш код лучше структурируется вокруг объектов с внутренним состоянием и методами.
  • Используйте функциональное разделение, если ваш код ориентирован на функции и выполнение задач, и если вам не нужны особенности объектно-ориентированного программирования.

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

Разница между null и undefined

undefined неожиданное отсутствие значения, а null — умышленное отсутствие значения

  • undefined указывает на переменную, которая была объявлена, но не инициализирована, в то время как null является значением, явно указывающим на отсутствие значения.

  • undefined используется для обозначения отсутствия значения по умолчанию, в то время как null используется для явного указания отсутствия значения.

Примитивы мутабельны?

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

Что такое +=&=|=, =, !!, != ?

  1. += - Оператор комбинированного присваивания сложения. Пример: a += b эквивалентно a = a + b. Он сложит значение переменной b с текущим значением переменной a и затем присвоит результат обратно в переменную a.

  2. &= - Оператор комбинированного присваивания побитового И (AND). Пример: a &= b эквивалентно a = a & b. Он выполняет побитовую операцию И между значением переменной a и значением переменной b. Результат будет присвоен обратно в переменную a.

  3. |= - Оператор комбинированного присваивания побитового ИЛИ (OR). Пример: a |= b эквивалентно a = a | b. Он выполняет побитовую операцию ИЛИ между значением переменной a и значением переменной b. Результат будет присвоен обратно в переменную a.

  4. = - Оператор присваивания. Пример: a = b присваивает значение переменной b переменной a.

  5. !! - Оператор преобразования в булево значение. Пример: !!a преобразует значение a в булево значение. Если a является "truthy" (значение, которое интерпретируется как истина), то !!a вернет true, в противном случае вернет false.

  6. != - Оператор сравнения на неравенство. Пример: a != b проверяет, не равны ли значения переменных a и b. Если значения не равны, оператор возвращает true, иначе false.

Чем отличается массив от кортежа?

Массивы:

  • Массивы это упорядоченная коллекция элементов.
  • Элементы массива имеют индексы, начинающиеся с 0, что позволяет легко получать доступ к элементам по их индексу.
  • Размер массива может быть изменен во время выполнения программы.
  • В массивах можно хранить значения разных типов данных (числа, строки, объекты и т.д.).
  • Предоставляет встроенные методы и свойства для работы с массивами, такие как length, push(), pop(), map(), filter() и другие.

Кортежи:

  • Кортежи также являются упорядоченной коллекцией элементов, но они фиксированы по размеру и типам данных элементов.
  • Элементы кортежа могут иметь значения разных типов данных, но их типы и количество элементов известны при объявлении кортежа.
  • Кортежи обычно используются для группировки связанных элементов в одно значение, где каждый элемент имеет свою семантику и значение.
  • Обращение к элементам происходит по индексу, как и в массивах.
  • Кортежи более типизированы и обычно используются, когда важен порядок элементов и их типы данных.

Деструктуризация объекта?

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

const person = {
name: 'John Doe',
age: 30,
address: {
city: 'New York',
country: 'USA'
}
};

Базовая деструктуризация

const { name, age } = person;

Деструктуризация вложенных объектов

const {
address: { city }
} = person;

Использование деструктуризации с функциями

function greet({ name, age }) {}

Деструктуризация с переименованием

const { name: personName, age: personAge } = person;

console.log(personName); // Выведет: John Doe
console.log(personAge); // Выведет: 30

Деструктуризация с значениями по умолчанию

const { name, age, gender = 'unknown' } = person;
console.log(gender); // Выведет: unknown, так как свойство gender не определено в объекте person

Как проверить на NaN?

isNaN(value);

Однако, isNaN() может дать непредсказуемые результаты для нечисловых значений.

value !== value;

Этот метод надежен, так как NaN является единственным значением, которое не равно самому себе.

Number.isNaN(value);

Этот метод является наиболее надежным, так как он проверяет, является ли значение именно NaN, без дополнительных преобразований типов.

В чем разница между операторами == и === ?

  • Оператор == (равенство):

  • Проверяет на абстрактное равенство, что означает, что он может выполнять преобразование типов перед сравнением.

  • Например, 1 == "1" вернет true, потому что число 1 и строка "1" считаются равными после преобразования типов.

  • Оператор === (строгое равенство):

  • Проверяет на строгое равенство, не выполняя преобразование типов.

  • Если два значения не одного типа, оператор === вернет false.

  • Например, 1 === "1" вернет false, так как число и строка не считаются равными без преобразования типов.

Использование === рекомендуется для сравнения, так как это помогает избежать неожиданных результатов из-за автоматического преобразования типов.

Разница между var let и const ?

var

  • Глобальная область видимости вне функций.

  • Отсутствует блочная область видимости.

  • Можно повторно объявлять.

  • Поднимается (hoisting) с undefined до инициализации.

    let

  • Блочная область видимости.

  • Нельзя повторно объявлять в одном блоке.

  • Не поднимается.

    const

  • Блочная область видимости.

  • Нельзя повторно объявлять в одном блоке.

  • Не поднимается.

  • Значение неизменяемо.

Методы массивов

  1. push(): добавляет один или несколько элементов в конец массива.
  2. pop(): удаляет и возвращает последний элемент массива.
  3. shift(): удаляет и возвращает первый элемент массива.
  4. unshift(): добавляет один или несколько элементов в начало массива.
  5. slice(): возвращает новый массив, содержащий копию части исходного массива.
  6. splice(): изменяет исходный массив, удаляя, заменяя или добавляя элементы.
  7. concat(): объединяет два или более массива, создавая новый массив.
  8. indexOf(): возвращает индекс первого вхождения указанного элемента в массиве.
  9. lastIndexOf(): возвращает индекс последнего вхождения указанного элемента в массиве.
  10. forEach(): выполняет заданную функцию один раз для каждого элемента массива.
  11. map(): создает новый массив, содержащий результат вызова заданной функции для каждого элемента исходного массива. Принимает коллбэк. item — элемент массива в текущей итерации; index — индекс текущего элемента; arr — сам массив, который мы перебираем
  12. filter(): создает новый массив, содержащий только те элементы исходного массива, для которых заданная функция возвращает true.
  13. reduce(): применяет заданную функцию к аккумулятору и каждому элементу массива (слева направо), возвращая одно окончательное значение.
  14. sort(): сортирует элементы массива в алфавитном порядке или по заданной функции сравнения.
  15. reverse(): меняет порядок элементов массива на обратный.

Всплытие и погружение

Всплытие и погружение — это два способа, как события распространяются в DOM:

  • Всплытие (Bubbling): Событие начинается с элемента, где оно произошло, и "всплывает" вверх по DOM-дереву, вызывая обработчики событий на каждом родительском элементе.

  • Погружение (Capturing): Событие начинается с самого верхнего элемента DOM-дерева и "погружается" вниз, вызывая обработчики событий на каждом дочернем элементе.

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

Замыкание

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

function createGreeter(greeting) {
return function (name) {
console.log(greeting + ', ' + name + '!');
};
}

const greeter = createGreeter('Привет');
greeter('Мир'); // Выведет: Привет, Мир!
function createCounter() {
let count = 0; // Эта переменная "замыкается" внутри функции counter

return function () {
count++; // Увеличиваем значение count
console.log(count); // Выводим текущее значение count
};
}

const counter = createCounter(); // Создаем счетчик

counter(); // Выводит: 1
counter(); // Выводит: 2
counter(); // Выводит: 3

В каких случаях однопоточность лучше многопоточности?

  1. Задача не может быть разбита на подзадачи.
  2. Потоки не используются в полной мере.
  3. Управление потоками добавляет сложности.
  4. Системные ресурсы ограничены.
  5. Важна простота и надежность кода.

Event Loop

Event Loop (Цикл событий) — это ключевой механизм в JavaScript, который позволяет выполнять асинхронные операции, такие как запросы к серверу, таймеры и другие задачи, не блокируя основной поток выполнения кода. Он координирует работу трех основных компонентов:

  1. Call Stack (Стек вызовов): Это механизм, который отслеживает выполнение функций в программе. Когда функция вызывается, она добавляется в стек вызовов. Когда функция завершает выполнение, она удаляется из стека.

  2. Web API (API, предоставляемый браузером): Это набор функций, предоставляемых браузером, которые могут выполняться асинхронно. Например, setTimeoutfetch и другие.

  3. Callback Queue (Очередь колбэков): Это очередь функций, которые должны быть выполнены после того, как основной поток выполнения (Call Stack) освободится.

Макротаски и микротаски — это два уровня очереди колбэков в JavaScript:

  • Макротаски: Очередь для асинхронных операций, таких как setTimeoutsetInterval, с задержкой выполнения.
  • Микротаски: Очередь для колбэков промисов и операций, которые должны выполняться как можно быстрее после текущего блока кода, с более высоким приоритетом по сравнению с макротасками (promise, mutationObserver, queveMicrotask(функция явно создает микротаску))

Цикл событий работает следующим образом:

  • Когда в стеке вызовов появляется функция, она выполняется.
  • Если в процессе выполнения функции встречается асинхронная операция (например, setTimeoutfetch), она передается в Web API, а функция завершает свое выполнение.
  • После завершения асинхронной операции, соответствующий колбэк добавляется в очередь колбэков.
  • Когда стек вызовов становится пустым, цикл событий берет первый колбэк из очереди колбэков и помещает его в стек вызовов для выполнения.

Этот процесс повторяется бесконечно, что позволяет JavaScript обрабатывать асинхронные операции и не блокировать основной поток выполнения кода.

Использование setTimeout с нулевой задержкой является одним из способов перенести выполнение кода в конец очереди колбэков, освободив основной поток для выполнения других задач. Это может быть полезно для оптимизации производительности, особенно в ситуациях, когда нужно выполнить код после того, как все синхронные операции завершились.

Promise

Promise в JavaScript — это объект, представляющий конечное состояние асинхронной операции. Он может находиться в одном из трех состояний:

  • Pending (ожидание): начальное состояние, операция еще не завершена.
  • Fulfilled (выполнено успешно): операция завершилась успешно.
  • Rejected (отклонено): операция завершилась с ошибкой.

Статические методы промисов

Promise.race(): запускает несколько промисов одновременно и возвращает новый промис, который выполнится как только первый из запущенных промисов завершится (успешно или с ошибкой).

Promise.all(): запускает несколько промисов одновременно и возвращает новый промис, который выполнится, когда все промисы завершатся успешно. Если хотя бы один промис отклонится, то возвращаемый промис также отклонится.

Promise.allSettled(): запускает несколько промисов одновременно и возвращает новый промис, который выполнится, когда все промисы завершатся, независимо от их результата. Возвращает массив объектов, каждый из которых содержит статус и значение каждого промиса.

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

Promise.race()

const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(value => {
console.log(value); // Выведет "two", так как promise2 завершился быстрее
});

Promise.all()

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
console.log(values); // Выведет [3, 42, "foo"]
});

Promise.allSettled()

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));

Promise.allSettled([promise1, promise2]).then(results => {
console.log(results);
// Выведет [{status: "fulfilled", value: 3}, {status: "rejected", reason: "foo"}]
});

Виды функций и их отличия

Function Declaration

  • Объявление: Использует ключевое слово function и имеет имя.
  • Глобальная область видимости: Если объявлена вне любой функции, становится глобальной функцией.
  • Контекст выполнения: Использует собственный контекст this, который может быть изменен в зависимости от того, как функция вызывается.
  • Пример:
function greet() {
console.log('Hello, World!');
}

Function Expression

  • Объявление: Функция объявляется как часть выражения, часто с использованием constlet или var.
  • Анонимность: Может быть анонимной, если не имеет имени.
  • Контекст выполнения: Использует контекст this из окружающего кода, что делает ее поведение более предсказуемым.
  • Пример:
const greet = function () {
console.log('Hello, World!');
};

Стрелочные функции

  • Объявление: Использует синтаксис стрелочной функции () => {}.
  • Контекст выполнения: Не имеет собственного this, использует this из окружающего контекста.
  • Использование в конструкторах: Не может быть использована как конструктор.
  • Доступ к arguments: Не имеет доступа к объекту arguments, использует arguments из окружающего контекста.
  • Пример:
const greet = () => {
console.log('Hello, World!');
};

IIFE (Immediately Invoked Function Expression)

  • Определение: Функция, которая самовызывается сразу после объявления.
  • Использование: Часто используется для создания закрытого пространства имен, изоляции переменных и функций от глобального контекста.
  • Пример:
(function () {
console.log('This is an IIFE!');
})();

Асинхронные функции

  • Объявление: Используют ключевое слово async перед объявлением функции.
  • Контекст выполнения: Позволяют использовать await для ожидания завершения промисов внутри функции.
  • Пример:
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}

Генераторы

  • Объявление: Используют ключевое слово function* для объявления функции-генератора.
  • Использование: Позволяют создавать итерируемые объекты, которые генерируют значения по мере их запроса.
  • Пример:
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}

const generator = idGenerator();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2

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

Чистая функция

Чистые функции — это концепция в программировании

  • Возвращает значение, которое зависит только от ее входных аргументов a и b.
  • В не изменяет состояние внешнего мира (например, не изменяет глобальные переменные, не взаимодействует с внешними ресурсами).
function add(a, b) {
return a + b;
}

Что такое контекст?

Контекст в JavaScript определяет окружение, в котором выполняется код. В контексте функции, this ссылается на объект, откуда функция была вызвана, позволяя доступ к его свойствам и методам. Потеря контекста (this) происходит, когда функция вызывается не как метод объекта, например, как обычная функция или через стрелочную функцию. В таких случаях this может ссылаться на глобальный объект или быть undefined (в строгом режиме).

Для изменения контекста используются методы callapply и bindcall и apply вызывают функцию с указанным контекстом и аргументами, а bind создает новую функцию с указанным контекстом.

Стрелочные функции не имеют своего this, они наследуют this из окружающего контекста. Асинхронные функции также имеют свой контекст this, который определяется в момент вызова. В глобальном контексте this ссылается на глобальный объект, но в строгом режиме this будет undefined. Когда функция вызывается через newthis ссылается на новый объект.

Примеры:

Использование call:

function greet() {
console.log(`Привет, ${this.name}!`);
}

const person = { name: 'Анна' };
greet.call(person); // Выведет: "Привет, Анна!"

Использование apply:

function sum(a, b) {
return a + b;
}

const numbers = [1, 2];
console.log(sum.apply(null, numbers)); // Выведет: 3

Использование bind:

function greet() {
console.log(`Привет, ${this.name}!`);
}

const person = { name: 'Анна' };
const boundGreet = greet.bind(person);
boundGreet(); // Выведет: "Привет, Анна!"

В методах call и bind можно передать this в качестве первого аргумента, чтобы явно указать контекст выполнения функции. Однако, если функция не зависит от контекста (this), то первый аргумент может быть опущен.

В методе apply, если вы хотите передать аргументы функции в виде массива, то this должен быть указан в качестве первого аргумента, а аргументы функции передаются вторым аргументом в виде массива. Если вы попытаетесь опустить первый аргумент, то JS будет воспринимать массив как this, поэтому нужно явно передавать null

Стрелочная функция:

const obj = {
value: 'Hello',
arrowFunction: () => {
console.log(this.value); // undefined, если не в строгом режиме, иначе ReferenceError
}
};

obj.arrowFunction();

Асинхронная функция:

const obj = {
value: 'Hello',
asyncFunction: async function () {
setTimeout(() => {
console.log(this.value); // 'Hello'
}, 1000);
}
};

obj.asyncFunction();

Глобальный контекст:

console.log(this); // window в браузере, undefined в строгом режиме

Использование new:

function Person(name) {
this.name = name;
}

const person = new Person('John');
console.log(person.name); // 'John'

Структуры данных

Массивы (Arrays)

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

let fruits = ['apple', 'banana', 'mango'];
console.log(fruits[0]); // Выведет 'apple'

Объекты (Objects)

Объекты используются для хранения неупорядоченных коллекций данных в виде пар ключ-значение. Ключи объекта могут быть строками или символами.

let person = {
name: 'John',
age: 30,
city: 'New York'
};
console.log(person.name); // Выведет 'John'

Map

Map представляет собой коллекцию ключ-значение, где ключи могут быть любого типа. Map сохраняет порядок вставки элементов.

let map = new Map();
map.set('name', 'John');
map.set('age', 30);
console.log(map.get('name')); // Выведет 'John'

Set

Set в JavaScript представляет собой коллекцию уникальных значений любого типа. Порядок элементов в Set определяется порядком их добавления.

let set = new Set();
set.add('apple');
set.add('banana');
console.log(set.has('apple')); // Выведет true

Стек (Stack)

Стек - это структура данных, которая следует принципу "последний вошел, первый вышел" (LIFO). В JavaScript стек можно реализовать с помощью массива.

class Stack {
constructor() {
this.items = [];
}

push(element) {
this.items.push(element);
}

pop() {
if (this.items.length === 0) return 'Underflow';
return this.items.pop();
}
}

let stack = new Stack();
stack.push(1);
stack.push(2);
console.log(stack.pop()); // Выведет 2

Очередь (Queue)

Очередь - это структура данных, которая следует принципу "первый вошел, первый вышел" (FIFO). В JavaScript очередь можно реализовать с помощью массива.

class Queue {
constructor() {
this.items = [];
}

enqueue(element) {
this.items.push(element);
}

dequeue() {
if (this.items.length === 0) return 'Underflow';
return this.items.shift();
}
}

let queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
console.log(queue.dequeue()); // Выведет 1

Деревья

Дерево - это иерархическая структура данных, которая состоит из узлов, связанных между собой. Узел дерева может иметь несколько дочерних узлов, но только один родительский узел.

class TreeNode {
constructor(value) {
this.value = value; // Значение узла
this.children = []; // Массив для хранения детей узла
}
}

const parent = new TreeNode('Родитель');
const child1 = new TreeNode('Ребёнок 1');
const child2 = new TreeNode('Ребёнок 2');

// Добавление "Ребёнок 1" и "Ребёнок 2" в детей узла "Родитель"
parent.children = [child1, child2];

const grandChild1 = new TreeNode('Внук 1');
const grandChild2 = new TreeNode('Внук 2');

// Добавление "Внук 1" в детей узла "Ребёнок 1"
child1.children = [grandChild1];

// Добавление "Внук 2" в детей узла "Ребёнок 2"
child2.children = [grandChild2];

// Функция для рекурсивного вывода дерева
function printTree(node, level = 0) {
console.log(' '.repeat(level * 2) + node.value);
node.children.forEach(child => printTree(child, level + 1));
}

printTree(parent);

Графы

Граф - это структура данных, состоящая из вершин (или узлов) и ребер, которые соединяют эти вершины.

// Определение класса Graph
class Graph {
// Конструктор класса, инициализирующий пустую список смежности
constructor() {
this.adjacencyList = {};
}

// Метод для добавления вершины в граф
addVertex(vertex) {
// Добавляем вершину в список смежности с пустым массивом
this.adjacencyList[vertex] = [];
}

// Метод для добавления ребра между двумя вершинами
addEdge(vertex1, vertex2) {
// Добавляем вершину vertex2 в список смежности вершины vertex1
this.adjacencyList[vertex1].push(vertex2);
// Добавляем вершину vertex1 в список смежности вершины vertex2
this.adjacencyList[vertex2].push(vertex1);
}

// Метод для вывода графа
printGraph() {
// Проходим по всем вершинам графа
for (let vertex in this.adjacencyList) {
// Выводим вершину и список смежных вершин
console.log(vertex + ' -> ' + this.adjacencyList[vertex].join(' '));
}
}
}

// Создание экземпляра класса Graph
let myGraph = new Graph();
// Добавление вершин 'A' и 'B' в граф
myGraph.addVertex('A');
myGraph.addVertex('B');
// Добавление ребра между вершинами 'A' и 'B'
myGraph.addEdge('A', 'B');
// Вывод графа
myGraph.printGraph();
// A -> B
// B -> A

Хеш-таблицы

Хеш-таблица — это структура данных, которая позволяет хранить пары ключ-значение. В JavaScript хеш-таблицы реализуются с помощью объектов. Это позволяет достигать быстрого доступа к данным.

class HashTable {
constructor() {
this.table = {};
}

// Метод для добавления пары ключ-значение
add(key, value) {
this.table[key] = value;
}

// Метод для получения значения по ключу
get(key) {
return this.table[key];
}

// Метод для проверки наличия ключа в хеш-таблице
has(key) {
return key in this.table;
}

// Метод для удаления пары ключ-значение по ключу
remove(key) {
delete this.table[key];
}

// Метод для вывода всех ключей и значений хеш-таблицы
print() {
for (let key in this.table) {
console.log(`Ключ: ${key}, Значение: ${this.table[key]}`);
}
}
}

// Пример использования класса HashTable
let myHashTable = new HashTable();

// Добавляем в хеш-таблицу пару ключ-значение
myHashTable.add('name', 'John Doe');

// Доступ к значению по ключу
myHashTable.get('name'); // Выведет: John Doe

// Удаление пары ключ-значение из хеш-таблицы
myHashTable.remove('name');

Связанные списки

Связанный список - это структура данных, состоящая из узлов, каждый из которых содержит данные и ссылку на следующий узел в списке.

// Определение класса Node для узла односвязного списка
class Node {
constructor(data) {
this.data = data; // Данные, хранящиеся в узле
this.next = null; // Ссылка на следующий узел в списке
}
}

// Создание головного узла списка
let head = new Node('Задача 1');
// Создание второго узла списка
let secondNode = new Node('Подзадача 1.1');
// Создание третьего узла списка
let thirdNode = new Node('Подзадача 1.1.2');

// Связывание узлов: головной узел ссылается на второй, второй узел ссылается на третий
head.next = secondNode;
secondNode.next = thirdNode;

// Вывод головного узла списка
console.log(head);

Полифиллы и Транспиляция

Полифиллы: Это код, который добавляет в браузер функции, которых там нет. Например, если браузер не поддерживает Promise, полифилл может добавить эту функцию.

Транспиляция: Это процесс преобразования кода, написанного на новой версии языка, в код, который может выполняться на старой версии. Например, если вы пишете код на JavaScript ES6, транспилятор преобразует его в код, который может выполняться в браузерах, поддерживающих только ES5.

Основные Различия:

  • Цель: Полифиллы добавляют в браузер функции, которых там нет. Транспиляция делает код, написанный на новой версии языка, совместимым с старыми версиями.
  • Процесс: Полифиллы добавляют или изменяют функциональность браузера. Транспиляция изменяет код, чтобы он соответствовал старым стандартам.
  • Использование: Полифиллы используются для поддержки новых возможностей в браузерах. Транспиляция используется для обеспечения совместимости кода написанного на новых версиях языка с браузерами, поддерживающими только старые версии.

Например, Promise и fetch API были добавлены в JavaScript после его создания, и для поддержки этих функций в старых браузерах были созданы полифиллы.

Или пример транспиляции с использованием Babel

Псевдомассивы

Это объекты, которые имеют свойство length и индексированные элементы, но не являются экземплярами Array. Они могут быть полезны в ситуациях, когда вам нужно работать с коллекцией элементов, но вы не хотите использовать методы массива, такие как pushpopshiftunshiftspliceforEachmapfilterreduce и т.д. Вместо этого, у псевдомассивов есть методы slice()concat()indexOf()includes(), и length.

function createPseudoArray(elements) {
let pseudoArray = {};
for (let i = 0; i < elements.length; i++) {
pseudoArray[i] = elements[i];
}
pseudoArray.length = elements.length;
return pseudoArray;
}

const elements = ['apple', 'banana', 'cherry'];
const pseudoArray = createPseudoArray(elements);

// Выводим содержимое псевдомассива
console.log(pseudoArray);
// Вывод: {0: "apple", 1: "banana", 2: "cherry", length: 3}

Статические методы

Статические методы - это методы класса, которые могут быть вызваны без создания экземпляра этого класса.

class MyClass {
static myStaticMethod() {
return 'Hello, world!';
}
}

console.log(MyClass.myStaticMethod()); // Выведет: Hello, world!

Прототип

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

function Person(name) {
this.name = name;
}

Person.prototype.sayHello = function () {
console.log(`Hello, my name is ${this.name}`);
};

const john = new Person('John');
john.sayHello(); // Выведет: Hello, my name is John

Прототипное наследование

Прототипное наследование в JavaScript позволяет объектам наследовать свойства и методы от других объектов через прототипы. Когда свойство или метод не найдены в объекте, JavaScript автоматически ищет их в его прототипе.

Глубокое копирование

  • В большинстве случаев подойдет JSON.parse(JSON.stringify(obj)), но такой подход не подойдет, если в объекте есть функции или циклические ссылки.
  • В библиотеке Lodash или Underscore есть метод cloneDeep, который позволяет копировать объект с вложенными массивами и объектами.
  • Использование библиотеки Immutable.js
  • Можно написать собственную функцию для глубокого копирования, используя рекурсию.

Чем отличается глубокое копирование от поверхностного?

Глубокое копирование (Deep Copy) создает полностью независимую копию исходного объекта, в то время как поверхностное копирование (Shallow Copy) создает новый объект с ссылками на исходные вложенные объекты, где изменения в копии влияют на оригинал.

Оператор Optional chaining( ?. )

Оператор Optional Chaining (?.) в JavaScript позволяет безопасно обращаться к свойствам объекта, которые могут быть неопределены, без генерации ошибок. Если свойство не существует, выражение возвращает undefined.

const user = { name: 'John' };
const city = user?.address?.city; // city будет undefined, без ошибок

Объект arguments

  • Определение: Массивоподобный объект внутри функций, содержащий переданные аргументы.
  • Доступ к аргументам: По индексу, например, arguments[0] для первого аргумента.
  • Преимущества:
    • Гибкость: Позволяет функциям принимать переменное количество аргументов.
    • Универсальность: Обеспечивает универсальность функций.
  • Ограничения:
    • Не является массивом: Нет доступа к методам массива, но может быть преобразован в массив.
    • Не поддерживается стрелочными функциями: В стрелочных функциях не доступен.
  • Преобразование в массив: Можно преобразовать с помощью Array.from(arguments) или [...arguments].
  • Пример преобразования:
function sum() {
const argsArray = Array.from(arguments);
return argsArray.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // Выведет: 10

Каррирование

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

  • Повторное использование кода: Мы можем создавать новые функции, специализируясь на конкретных значениях аргументов.
  • Читаемость: Код становится более понятным, когда функции специализируются на конкретных задачах.
  • Гибкость: Мы можем легко изменять поведение функции, применяя частичные аргументы.
// У нас есть функция, которая умножает два числа

function multiply(x, y) {
return x * y;
}
// Превратим ее в каррированную версию

function multiply(x) {
return function (y) {
return x * y;
};
}

const double = multiply(2); // Создаем функцию, которая умножает на 2
console.log(double(5)); // Выведет: 10
// Сокращенная запись

const multiply = x => y => x * y;

console.log(multiply(2)(5)); // Выведет: 10

Как подгружаются скрипты в js

Синхронная загрузка

  • Стандартное поведение: Скрипты загружаются синхронно, что означает, что браузер останавливает построение DOM-дерева, пока скрипт не будет полностью загружен и выполнен. Это может привести к задержкам в отображении страницы, если скрипты тяжелые.

Атрибут defer

  • Использование: Добавляется к тегу <script> для указания браузеру продолжать загрузку скрипта, когда DOM-дерево будет построено. Это позволяет скриптам загружаться параллельно с построением DOM, но выполняться только после его завершения.
  • Пример<script src="script.js" defer></script>

Атрибут async

  • Использование: Также добавляется к тегу <script>, но позволяет скрипту загружаться и выполняться асинхронно, независимо от построения DOM-дерева. Это означает, что скрипт может загрузиться и выполниться в любое время, не блокируя построение страницы.
  • Пример<script src="script.js" async></script>
  • Идеально подходит для: Скриптов, которые не влияют на базовый функционал приложения и их загрузку не нужно обязательно дожидаться, например, скриптов для загрузки рекламы.

Hoisting, Temporal dead zone и Scoup

Hoisting (Поднятие)

  • Определение: В JavaScript, процесс, при котором объявления переменных и функций "поднимаются" в начало их области видимости.
  • Пример:
console.log(x); // undefined
var x = 5;

Здесь переменная x объявлена после её использования, но из-за hoisting, она "поднимается" в начало области видимости, и её значение становится undefined до присвоения.

Temporal Dead Zone (Временная мертвая зона)

  • Определение: Временной интервал между началом области видимости переменной (например, блока кода) и моментом её объявления, в течение которого к переменной нельзя обращаться.
  • Пример:
console.log(y); // ReferenceError: y is not defined
let y = 10;

Переменная y, объявленная через let, находится в TDZ до её объявления, поэтому попытка обратиться к ней вызывает ошибку.

Scope (Область видимости)

  • Определение: Область кода, в которой переменная или функция доступна.
  • Пример:
function example() {
var x = 10;
if (true) {
var x = 20; // "поднимается" в область видимости функции example
console.log(x); // Выведет: 20
}

console.log(x); // Выведет: 20
}
example();

В этом примере переменная x объявлена внутри блока if, но из-за hoisting она доступна внутри всей функции example.

LocaleCompare

Метод localeCompare в JavaScript предназначен для сравнения двух строк в соответствии с правилами сортировки текущего языка (по умолчанию, это язык браузера пользователя). Этот метод возвращает число, которое указывает на результат сравнения:

  • Возвращает -1, если первая строка меньше второй.
  • Возвращает 0, если строки равны.
  • Возвращает 1, если первая строка больше второй.

Метод localeCompare может принимать дополнительные параметры:

  1. Язык сравнения: Строка, указывающая язык, на котором следует проводить сравнение. Этот параметр необязателен и по умолчанию используется язык браузера.
  2. Опции сравнения: Объект, содержащий дополнительные параметры для сравнения, такие как sensitivity (чувствительность к регистру), ignorePunctuation (игнорирование знаков препинания) и другие.

Observer API в JavaScript

  1. Mutation Observer API: Позволяет отслеживать изменения в DOM-структуре, включая добавление или удаление узлов, изменение атрибутов и другие изменения.

  2. Intersection Observer API: Позволяет отслеживать изменения видимости элементов на странице. Это может быть полезно для реализации ленивой загрузки изображений или инфинити скроллинга.

  3. Resize Observer API: Позволяет отслеживать изменения размеров элементов. Это может быть полезно для адаптивного дизайна или для реализации функционала, зависящего от размеров элементов.

  4. PerformanceObserver API: Позволяет отслеживать производительность веб-приложения, включая загрузку ресурсов, выполнение JavaScript и другие аспекты работы приложения.

  5. Service Worker API: Позволяет создавать фоновые скрипты, которые могут запускаться даже когда веб-страница не открыта в браузере, обеспечивая такие возможности, как кэширование ресурсов и работа в оффлайн-режиме.

use strict

Директива use strict в JavaScript включает строгий режим, делая код более предсказуемым и безопасным. В строгом режиме:

  • Ошибки вызываются при использовании неопределенных переменных.
  • Ошибки при попытке присвоить значение глобальным переменным, которые не были объявлены.
  • Ошибки при использовании зарезервированных слов в качестве идентификаторов.
  • Ошибки при попытке изменить свойства, которые не могут быть изменены.
  • Ошибки при выполнении недопустимых операций.

Чтобы включить строгий режим, добавьте "use strict"; в начало файла или функции.

Как отслеживать scroll?

window.scrollY - можем вытащить текущее значения и записать его в useState

Функция throttle?

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

В чем отличие package.json и package.lock.json?

  • package.json содержит описание проекта и его зависимостей в виде диапазонов версий, что позволяет автоматически получать обновления.

  • package-lock.json генерируется автоматически при установке зависимостей и содержит точные версии всех установленных зависимостей, обеспечивая воспроизводимость установки.

package-lock.json должен быть включен в систему контроля версий для согласованности установки зависимостей.

TreeShacking

TreeShacking - это техника оптимизации кода в JS, которая позволяет удалять из бандла неиспользуемый код (функции, классы, переменные), которые не используются в приложении. Tree Shaking определяет зависимости между модулями, которые используются в приложении, что позволяет уменьшить размер время загрузки бандла. Tree shaking доступен в Webpack.

Что такое Абстрактное синтаксическое дерево (AST)?

Абстрактное синтаксическое дерево (AST) — это структура данных, представляющая код программы в виде дерева. Каждый узел дерева соответствует конструкции языка, например, операции или объявления.

Основные компоненты:

  • Узлы (Nodes): Каждый узел в AST представляет собой конструкцию языка, такую как операция, объявление переменной, вызов функции и т.д.
  • Корневой узел (Root Node): Начальная точка AST, откуда начинается анализ кода.
  • Листья (Leaves): Узлы, которые не имеют дочерних элементов, обычно представляют собой значения или идентификаторы.
  • Ветви (Branches): Узлы, имеющие дочерние элементы, представляют собой операторы или структуры языка.

Процесс создания AST:

  1. Лексический анализ (Lexical Analysis): Исходный код разбивается на токены (например, ключевые слова, идентификаторы, операторы).
  2. Синтаксический анализ (Syntax Analysis): Токены организуются в соответствии с правилами грамматики языка, формируя AST.

Применение AST:

  • Компиляция: AST используется компиляторами для преобразования кода в машинный код или другой язык.
  • Интерпретация: В случае интерпретируемых языков, AST позволяет движку JavaScript выполнять код построчно, понимая его структуру.

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

  • Быстродействие: Позволяет движку быстро и точно понимать структуру кода.
  • Интерпретируемость: Поддерживает интерпретацию кода, позволяя его выполняться построчно.
  • Поддержка современных языковых конструкций: Позволяет современным компиляторам поддерживать новые языковые конструкции.

В каких случаях однопоточность лучше многопоточности?

  1. Задача не может быть разбита на подзадачи.
  2. Потоки не используются в полной мере.
  3. Управление потоками добавляет сложности.
  4. Системные ресурсы ограничены.
  5. Важна простота и надежность кода.

Boxing and Unboxing

В JS нет явного понятия boxing и unboxing, как Java или C#. ‘Boxing’ означает обернуть примитив в объектную-обертку, ‘Unboxing’ - обратное извлечение примитива из объектной обертки.

В JavaScript, "boxing" и "unboxing" происходят автоматически:

  • Boxing: JavaScript создает временные объекты-обертки для примитивов при вызове методов.
let str = 'Hello';
console.log(str.toUpperCase()); // Автоматически создает объект-обертку для строки
  • Unboxing: При использовании примитивов в контексте объектов, JavaScript преобразует объекты обратно в примитивы.
let num = new Number(10);
console.log(num + 5); // Преобразует объект-обертку обратно в число

Что такое регулярное выражение и для чего они нужны?

Регулярное выражение (regular expression) - это специальный текстовый шаблон, используемый для поиска, замены и извлечения информации из текста. Они широко применяются в программировании и веб-разработке для:

  1. Поиска и извлечения определенных частей текста.
  2. Проверки формата введенных данных (например, email-адресов).
  3. Замены частей текста.
  4. Фильтрации данных.
  5. Валидации данных.

Регулярные выражения состоят из метасимволов и специальных конструкций, задающих шаблоны для поиска или замены. Синтаксис регулярных выражений может отличаться в разных языках программирования.

EcmaScript

JavaScript, созданный Бренданом Эйхом в 1995 году, развивался под стандартом ECMAScript, разработанным и поддерживаемым Ecma International. Стандарт ECMAScript служит основой для JavaScript и его версии напрямую влияют на развитие и эволюцию языка.

  • ECMAScript 1 (1997): Первая версия ECMAScript, включающая базовые функции JavaScript, такие как переменные, функции и операторы управления потоком.

  • ECMAScript 2 (1998): Вторая версия, в основном сосредоточенная на улучшении стандарта и его согласованности.

  • ECMAScript 3 (1999): Введение регулярных выражений, операторов try-catch и улучшенного обработки строк.

  • ECMAScript 4 (Не выпущен): Планировался как четвертая версия, но не был выпущен из-за значительного сопротивления.

  • ECMAScript 5 (2009): Введение строгого режима, поддержки JSON и улучшенного управления массивами.

  • ECMAScript 5.1 (2011): Небольшое обновление для ES5, сосредоточенное на улучшении стандарта и исправлении ошибок.

  • ECMAScript 6 (2015): Введение let и const, стрелочных функций, шаблонных строк и классов.

  • ECMAScript 2016 (ES7): Введение оператора возведения в степень и Array.prototype.includes().

  • ECMAScript 2017 (ES8): Введение async/await, Object.values(), Object.entries() и заполнения строк.

  • ECMAScript 2018 (ES9): Введение асинхронной итерации, Promise.prototype.finally(), и операторов расширения/сжатия.

  • ECMAScript 2019 (ES10): Введение Array.prototype.flat(), Array.prototype.flatMap(), и Object.fromEntries().

  • ECMAScript 2020 (ES11): Введение BigInt для больших целых чисел, Promise.allSettled(), Nullish Coalescing Operator (??), и Optional Chaining (?.).

  • ECMAScript 2021 (ES12): Введение дополнительных логических операторов присваивания, числовых разделителей, Promise.any(), и String.prototype.replaceAll().

  • ECMAScript 2022 (ES13): Введение возможности использования top-level await, новых элементов класса, статических блоков в классах, синтаксиса #x в obj для проверки наличия приватных полей на объектах, индексов совпадений регулярных выражений через флаг /d, свойства cause на объектах Error, метода at для строк, массивов и TypedArray, и Object.hasOwn.

  • ECMAScript 2023 (ES14): Введение методов toSorted, toReversed, with, findLast, и findLastIndex на Array.prototype и TypedArray.prototype, а также метода toSpliced на Array.prototype. Поддержка комментариев "#!" в начале файлов и использование большинства Symbols как ключей в слабых коллекциях.