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