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, а также управления контекстом выполнения и областью видимости переменных.