Архивный материал. Актуальный структурный раздел: перейти к карте подготовки.
JavaScript
Этот раздел сохранен как архивный справочник.
Для системной подготовки используйте JavaScript и Банк вопросов.
Типы данных:
Примитивные:
String - строка Number - число Boolean - true/false (логический тип) BigInt - большое число Undefined - значение не установлено Null - отсутствие значения Symbol - представляет уникальное значение, которое часто применяется для обращения к свойствам сложных объектов
Ссылочные:
Object - объект
ООП
Основные принципы объектно-ориентированного программирования (ООП) в JavaScript
Классы и объекты: Классы представляют собой шаблоны, описывающие атрибуты (поля) и методы (функции) объектов. Объекты являются конкретными экземплярами классов.
Инкапсуляция: Инкапсуляция это концепция, позволяющая скрыть детали реализации объекта и предоставить доступ только к необходимым свойствам и методам.
Наследование: Наследование позволяет создавать новые классы на основе существующих, наследуя их атрибуты и методы. Это позволяет повторно использовать код и создавать иерархии классов, добавляя новый функционал.
Полиморфизм: Полиморфизм позволяет объектам с одинаковым интерфейсом вести себя по-разному в зависимости от их типа или контекста использования.
Абстракция: Абстракция позволяет скрыть сложность реализации объектов за простым интерфейсом. Это делает код более понятным и удобным для использования.
Эти концепции помогают разработчикам создавать более чистый, модульный и гибкий код, что облегчает его понимание, поддержку и масштабирование.
Классы и функциональное разделение:
Когда речь идет о структурировании кода, часто выделяют два основных подхода: классы и функциональное разделение.
-
Классы:
- Описание: Классы представляют собой основной инструмент объектно-ориентированного программирования (ООП). Они объединяют данные и методы, работающие с этими данными, внутри одной структуры.
- Пример:
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 примитивы (числа, строки, булевы значения, null, undefined) являются неизменяемыми. Это означает, что после создания значения, его нельзя изменить. Любые операции, которые кажутся изменяющими значение, на самом деле создают новое значение и присваивают его переменной.
Что такое +=, &=, |=, =, !!, != ?
-
+=- Оператор комбинированного присваивания сложения. Пример:a += bэквивалентноa = a + b. Он сложит значение переменнойbс текущим значением переменнойaи затем присвоит результат обратно в переменнуюa. -
&=- Оператор комбинированного присваивания побитового И (AND). Пример:a &= bэквивалентноa = a & b. Он выполняет побитовую операцию И между значением переменнойaи значением переменнойb. Результат будет присвоен обратно в переменнуюa. -
|=- Оператор комбинированного присваивания побитового ИЛИ (OR). Пример:a |= bэквивалентноa = a | b. Он выполняет побитовую операцию ИЛИ между значением переменнойaи значением переменнойb. Результат будет присвоен обратно в переменнуюa. -
=- Оператор присваивания. Пример:a = bприсваивает значение переменнойbпеременнойa. -
!!- Оператор преобразования в булево значение. Пример:!!aпреобразует значениеaв булево значение. Еслиaявляется "truthy" (значение, которое интерпретируется как истина), то!!aвернетtrue, в противном случае вернетfalse. -
!=- Оператор сравнения на неравенство. Пример: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 -
Блочная область видимости.
-
Нельзя повторно объявлять в одном блоке.
-
Не поднимается.
-
Значение неизменяемо.
Методы массивов
- push(): добавляет один или несколько элементов в конец массива.
- pop(): удаляет и возвращает последний элемент массива.
- shift(): удаляет и возвращает первый элемент массива.
- unshift(): добавляет один или несколько элементов в начало массива.
- slice(): возвращает новый массив, содержащий копию части исходного массива.
- splice(): изменяет исходный массив, удаляя, заменяя или добавляя элементы.
- concat(): объединяет два или более массива, создавая новый массив.
- indexOf(): возвращает индекс первого вхождения указанного элемента в массиве.
- lastIndexOf(): возвращает индекс последнего вхождения указанного элемента в массиве.
- forEach(): выполняет заданную функцию один раз для каждого элемента массива.
- map(): создает новый массив, содержащий результат вызова заданной функции для каждого элемента исходного массива. Принимает коллбэк. item — элемент массива в текущей итерации; index — индекс текущего элемента; arr — сам массив, который мы перебираем
- filter(): создает новый массив, содержащий только те элементы исходного массива, для которых заданная функция возвращает true.
- reduce(): применяет заданную функцию к аккумулятору и каждому элементу массива (слева направо), возвращая одно окончательное значение.
- sort(): сортирует элементы массива в алфавитном порядке или по заданной функции сравнения.
- 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
В каких случаях однопоточность лучше многопоточности?
- Задача не может быть разбита на подзадачи.
- Потоки не используются в полной мере.
- Управление потоками добавляет сложности.
- Системные ресурсы ограничены.
- Важна простота и надежность кода.
Event Loop
Event Loop (Цикл событий) — это ключевой механизм в JavaScript, который позволяет выполнять асинхронные операции, такие как запросы к серверу, таймеры и другие задачи, не блокируя основной поток выполнения кода. Он координирует работу трех основных компонентов:
-
Call Stack (Стек вызовов): Это механизм, который отслеживает выполнение функций в программе. Когда функция вызывается, она добавляется в стек вызовов. Когда функция завершает выполнение, она удаляется из стека.
-
Web API (API, предоставляемый браузером): Это набор функций, предоставляемых браузером, которые могут выполняться асинхронно. Например,
setTimeout,fetchи другие. -
Callback Queue (Очередь колбэков): Это очередь функций, которые должны быть выполнены после того, как основной поток выполнения (Call Stack) освободится.
Макротаски и микротаски — это два уровня очереди колбэков в JavaScript:
- Макротаски: Очередь для асинхронных операций, таких как
setTimeout,setInterval, с задержкой выполнения. - Микротаски: Очередь для колбэков промисов и операций, которые должны выполняться как можно быстрее после текущего блока кода, с более высоким приоритетом по сравнению с макротасками (promise, mutationObserver, queveMicrotask(функция явно создает микротаску))
Цикл событий работает следующим образом:
- Когда в стеке вызовов появляется функция, она выполняется.
- Если в процессе выполнения функции встречается асинхронная операция (например,
setTimeout,fetch), она передается в 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
- Объявление: Функция объявляется как часть выражения, часто с использованием
const,letили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 (в строгом режиме).
Для изменения контекста используются методы call, apply и bind. call и apply вызывают функцию с указанным контекстом и аргументами, а bind создает новую функцию с указанным контекстом.
Стрелочные функции не имеют своего this, они наследуют this из окружающего контекста. Асинхронные функции также имеют свой контекст this, который определяется в момент вызова. В глобальном контексте this ссылается на глобальный объект, но в строгом режиме this будет undefined. Когда функция вызывается через new, this ссылается на новый объект.
Примеры:
Использование 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. Они могут быть полезны в ситуациях, когда вам нужно работать с коллекцией элементов, но вы не хотите использовать методы массива, такие как push, pop, shift, unshift, splice, forEach, map, filter, reduce и т.д. Вместо этого, у псевдомассивов есть методы 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 может принимать дополнительные параметры:
- Язык сравнения: Строка, указывающая язык, на котором следует проводить сравнение. Этот параметр необязателен и по умолчанию используется язык браузера.
- Опции сравнения: Объект, содержащий дополнительные параметры для сравнения, такие как
sensitivity(чувствительность к регистру),ignorePunctuation(игнорирование знаков препинания) и другие.
Observer API в JavaScript
-
Mutation Observer API: Позволяет отслеживать изменения в DOM-структуре, включая добавление или удаление узлов, изменение атрибутов и другие изменения.
-
Intersection Observer API: Позволяет отслеживать изменения видимости элементов на странице. Это может быть полезно для реализации ленивой загрузки изображений или инфинити скроллинга.
-
Resize Observer API: Позволяет отслеживать изменения размеров элементов. Это может быть полезно для адаптивного дизайна или для реализации функционала, зависящего от размеров элементов.
-
PerformanceObserver API: Позволяет отслеживать производительность веб-приложения, включая загрузку ресурсов, выполнение JavaScript и другие аспекты работы приложения.
-
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:
- Лексический анализ (Lexical Analysis): Исходный код разбивается на токены (например, ключевые слова, идентификаторы, операторы).
- Синтаксический анализ (Syntax Analysis): Токены организуются в соответствии с правилами грамматики языка, формируя AST.
Применение AST:
- Компиляция: AST используется компиляторами для преобразования кода в машинный код или другой язык.
- Интерпретация: В случае интерпретируемых языков, AST позволяет движку JavaScript выполнять код построчно, понимая его структуру.
Преимущества использования AST:
- Быстродействие: Позволяет движку быстро и точно понимать структуру кода.
- Интерпретируемость: Поддерживает интерпретацию кода, позволяя его выполняться построчно.
- Поддержка современных языковых конструкций: Позволяет современным компиляторам поддерживать новые языковые конструкции.
В каких случаях однопоточность лучше многопоточности?
- Задача не может быть разбита на подзадачи.
- Потоки не используются в полной мере.
- Управление потоками добавляет сложности.
- Системные ресурсы ограничены.
- Важна простота и надежность кода.
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) - это специальный текстовый шаблон, используемый для поиска, замены и извлечения информации из текста. Они широко применяются в программировании и веб-разработке для:
- Поиска и извлечения определенных частей текста.
- Проверки формата введенных данных (например, email-адресов).
- Замены частей текста.
- Фильтрации данных.
- Валидации данных.
Регулярные выражения состоят из метасимволов и специальных конструкций, задающих шаблоны для поиска или замены. Синтаксис регулярных выражений может отличаться в разных языках программирования.
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 как ключей в слабых коллекциях.