Frontend | Вопросы собесов
19.1K subscribers
33 photos
1 video
1.1K links
Download Telegram
🤔 Как мы будем через js анимировать что либо?

В JavaScript есть три основных способа создания анимаций:

CSS-анимации (transition, @keyframes) + изменение классов через JS
Метод requestAnimationFrame()
Использование библиотек (GSAP, Anime.js, Velocity.js)

🟠CSS-анимации + управление через JS
Самый простой способ — использовать CSS-анимации, а в JS только добавлять или убирать классы.
<button onclick="toggleBox()">Анимировать</button>
<div id="box"></div>

<style>
#box {
width: 100px;
height: 100px;
background: red;
transition: transform 0.5s ease-in-out;
}
.move {
transform: translateX(200px);
}
</style>

<script>
function toggleBox() {
document.getElementById("box").classList.toggle("move");
}
</script>


🟠`requestAnimationFrame()` — лучший способ в чистом JS
Если нужно более гибкое управление анимацией, используем requestAnimationFrame().
<button onclick="startAnimation()">Старт</button>
<div id="box"></div>

<style>
#box {
width: 50px;
height: 50px;
background: blue;
position: absolute;
}
</style>

<script>
let position = 0;
let animationId;

function animate() {
position += 5; // Двигаем на 5px за кадр
document.getElementById("box").style.transform = `translateX(${position}px)`;

if (position < 300) { // Останавливаем, когда достигнет 300px
animationId = requestAnimationFrame(animate);
}
}

function startAnimation() {
cancelAnimationFrame(animationId); // Останавливаем предыдущую анимацию
position = 0;
animate();
}
</script>


🟠Библиотеки (GSAP, Anime.js, Velocity.js)
Если нужно делать мощные анимации с минимальным кодом, лучше использовать библиотеку.
<button onclick="animateBox()">Анимировать</button>
<div id="box"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

<script>
function animateBox() {
gsap.to("#box", { x: 300, rotation: 360, duration: 2, ease: "elastic.out(1,0.3)" });
}
</script>


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🤔 Как создать поток Web Worker в JavaScript?

Создаётся через объект Worker, указывая путь к JS-файлу. Этот файл будет исполняться в отдельном потоке и обмениваться данными с основным с помощью сообщений.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍4
🤔 Как изменить стили кнопок с атрибутом disabled?

C атрибутом disabled, используя селекторы атрибутов. Селектор [disabled] позволяет выбрать все элементы, которые имеют этот атрибут, а селектор :disabled позволяет выбрать все элементы, которые находятся в состоянии disabled.

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

🟠Селектора [disabled]
Этот селектор выбирает все элементы, у которых присутствует атрибут disabled.
button[disabled] {
background-color: gray;
color: white;
border: 1px solid #ccc;
cursor: not-allowed; /* Указывает, что элемент недоступен для взаимодействия */
opacity: 0.6; /* Уменьшает непрозрачность */
}


🟠Псевдокласса :disabled
Этот селектор выбирает все элементы, которые находятся в состоянии disabled.
button:disabled {
background-color: gray;
color: white;
border: 1px solid #ccc;
cursor: not-allowed;
opacity: 0.6;
}


🚩Полные примеры

С HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Disabled Button Styling</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<button>Active Button</button>
<button disabled>Disabled Button</button>
</body>
</html>


С CSS
/* Стили для активных кнопок */
button {
background-color: blue;
color: white;
border: 1px solid #000;
padding: 10px 20px;
cursor: pointer;
}

/* Стили для кнопок с атрибутом disabled */
button[disabled] {
background-color: gray;
color: white;
border: 1px solid #ccc;
cursor: not-allowed;
opacity: 0.6;
}

/* Альтернативный способ с использованием псевдокласса :disabled */
button:disabled {
background-color: gray;
color: white;
border: 1px solid #ccc;
cursor: not-allowed;
opacity: 0.6;
}


🟠Комбинированный подход
Вы можете комбинировать селекторы атрибутов с другими селекторами для более точного применения стилей.
/* Стили для кнопок с классом .special, которые имеют атрибут disabled */
button.special[disabled] {
background-color: darkgray;
color: black;
border: 1px dashed #999;
cursor: not-allowed;
opacity: 0.7;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1
🤔 Чем отличается блочный элемент от инлайнового?

- Блочный (block) — занимает всю ширину родителя, начинается с новой строки.
- Инлайновый (inline) — занимает только нужную ширину, располагается в строке с другими.
Примеры:
<div> — блочный, <span> — инлайновый.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥2
🤔 Является ли, drag and drop частью спецификации?

Да! Drag and Drop (перетаскивание) — это часть спецификации HTML5. В браузерах есть встроенный API, который позволяет делать перетаскивание элементов без дополнительных библиотек.

🚩Как работает Drag and Drop API?

В HTML5 можно сделать элемент перетаскиваемым, добавив ему атрибут draggable="true". Затем срабатывают специальные события, такие как dragstart, drop, dragover и другие.
<div id="drag-item" draggable="true">Перетащи меня</div>
<div id="drop-zone">Сюда можно сбросить</div>

<script>
const dragItem = document.getElementById("drag-item");
const dropZone = document.getElementById("drop-zone");

dragItem.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", "Данные элемента");
});

dropZone.addEventListener("dragover", (event) => {
event.preventDefault(); // Нужно, чтобы разрешить сброс
});

dropZone.addEventListener("drop", (event) => {
event.preventDefault();
const data = event.dataTransfer.getData("text/plain");
alert("Элемент сброшен: " + data);
});
</script>


🚩Какие события есть в Drag and Drop?

Основные события:
dragstart — когда начали тянуть элемент.
drag — когда элемент двигается (срабатывает много раз).
dragenter — когда курсор заходит в зону сброса.
dragover — когда элемент находится над зоной (нужно preventDefault()).
dragleave — когда курсор покидает зону сброса.
drop — когда элемент отпущен в зоне сброса.
dragend — когда перетаскивание завершено (даже если не сбросили).

🚩Где используется Drag and Drop?

- Перетаскивание файлов в <input type="file"> (например, загрузка изображений).
- Канбан-доски, как в Trello.
- Онлайн-редакторы, как Figma.
- Игры, где можно перемещать предметы.

🚩Можно ли сделать Drag and Drop без HTML5 API?

Да, можно использовать другие методы:
Через события мыши (mousedown, mousemove, mouseup).
Через CSS position: absolute и transform.
Через JS-библиотеки (Sortable.js, React DnD, Draggable.js).
Например, в мобильных браузерах стандартный Drag and Drop API плохо работает, и там лучше использовать события touchstart, touchmove, touchend.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥1
🤔 Какие использовал шаблоны в HTML?

- layout шаблоны — шапка, футер, контейнер;
- формы — шаблонные блоки для ввода;
- таблицы, карточки — повторяющиеся элементы;
- Использование <template> — для динамической вставки или фреймворков (например, Vue).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
🤔 Почему у нас в css нет селектора на родителе?

В CSS нет селектора родителя, потому что это нарушило бы оптимизацию и производительность рендеринга.

🚩Как работают селекторы в CSS?

Обычные селекторы идут от родителя к дочерним элементам.
.parent .child {
color: red;
}


Что было бы с селектором родителя?
.child:has-parent(.parent) { 
color: red;
}


🚩Использовать `:has()` (в современных браузерах)

В CSS4 появился :has(), который позволяет изменять родителя, если в нём есть определённый потомок.
.parent:has(.child) {
border: 2px solid red;
}


🚩Использовать Flexbox/Grid вместо селектора родителя

Иногда можно поменять структуру HTML и стилизацию, чтобы избежать проблемы.
.parent {
display: flex;
gap: 10px;
}


🚩Использовать JavaScript

Если нужно изменить родителя динамически, можно использовать JavaScript.
document.querySelectorAll(".child").forEach(child => {
child.parentElement.classList.add("has-child");
});

.has-child {
border: 2px solid blue;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🤔 Как можно гибко изменять размеры flex-элементов?

С помощью:
- flex-grow — распределение свободного пространства;
- flex-shrink — сжатие при нехватке места;
- flex-basis — базовая ширина перед распределением;
- или flex — сокращённой записи всех трёх параметров.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥3
🤔 Как с помощью js можно передвигать вперед/назад по истории браузера?

В JavaScript можно перемещаться вперед и назад по истории браузера с помощью объекта window.history.

🟠Использование `history.back()` и `history.forward()`
Эти методы аналогичны кнопкам "Назад" и "Вперед" в браузере.
history.back();    // Перемещение на одну страницу назад
history.forward(); // Перемещение на одну страницу вперед


Пример кнопок "Назад" и "Вперед"
<button onclick="history.back()">⬅️ Назад</button>
<button onclick="history.forward()">Вперед ➡️</button>


🟠Использование `history.go(n)`
Этот метод позволяет перемещаться на определенное количество шагов:
history.go(-1) – назад на 1 страницу
history.go(1) – вперед на 1 страницу
history.go(-2) – назад на 2 страницы
history.go(-2); // Перейти на две страницы назад
history.go(3); // Перейти на три страницы вперед


🟠Получение длины истории `history.length`
Если нужно узнать, сколько страниц в истории текущей сессии:
console.log(history.length); // Количество записей в истории


🟠Манипуляции с историей: `pushState()` и `replaceState()`
Если нужно изменить URL без перезагрузки страницы, можно использовать:
history.pushState(state, title, url)
Добавляет новый URL в историю (как будто пользователь перешел по ссылке).
history.pushState({ page: 1 }, "Title 1", "/page1");


history.replaceState(state, title, url)
Заменяет текущий URL (не добавляет новую запись в историю).
history.replaceState({ page: 2 }, "Title 2", "/page2");


Пример динамического изменения истории:
document.querySelector("button").addEventListener("click", () => {
history.pushState({ page: "about" }, "About Page", "/about");
});


🟠Отслеживание изменений истории `popstate`
Когда пользователь нажимает "Назад" или "Вперед", можно реагировать с помощью события popstate
window.addEventListener("popstate", (event) => {
console.log("Текущий state:", event.state);
});


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
🤔 Как CSS-спрайты реализуются на странице или сайте?

- Подключается одна большая картинка;
- Через background-image и background-position отображается нужный участок изображения на каждом элементе.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍5💊1
🤔 Какие знаешь подходы по написанию css?

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

🟠Классический (обычный) CSS
Стандартное написание CSS без структурных ограничений.
.button {
background-color: blue;
color: white;
padding: 10px;
}


🟠BEM (Block Element Modifier)
BEM — один из самых популярных методологий. Он помогает избежать конфликтов классов и улучшает читаемость кода.
- Block (Блок) — независимый компонент (.button).
- Element (Элемент) — часть блока (.button__icon).
- Modifier (Модификатор) — изменение блока (.button--primary).
<button class="button button--primary">
<span class="button__icon"></span> Нажми меня
</button>

.button {
background-color: gray;
color: white;
padding: 10px;
}

.button--primary {
background-color: blue;
}

.button__icon {
margin-right: 5px;
}


🟠OOCSS (Object-Oriented CSS)
OOCSS (Объектно-ориентированный CSS) предлагает разбивать стили на структуру и внешний вид, чтобы повторно использовать код.
.card {
border: 1px solid #ccc;
padding: 10px;
}

.card--dark {
background-color: black;
color: white;
}


🟠CSS-in-JS (Styled Components, Emotion)
Этот подход популярен в React и других фреймворках, где стили пишутся внутри JavaScript.
import styled from 'styled-components';

const Button = styled.button`
background-color: blue;
color: white;
padding: 10px;
`;

export default function App() {
return <Button>Нажми меня</Button>;
}


🟠Utility-First (Tailwind CSS)
В этом подходе используются готовые утилитарные классы для быстрого стилизации.
<button class="bg-blue-500 text-white p-2 rounded">Кнопка</button>


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🤔 В чём разница между системами, приближёнными к реальному времени, и системами реального времени на производстве?

- Системы реального времени (hard real-time) — критично соблюдают сроки отклика, любое опоздание недопустимо (например, в авиации, медицине, автоматике).
- Приближённые к реальному времени (soft real-time) — желательно быстрое выполнение, но небольшие задержки допустимы (например, стриминг видео, веб-приложения).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
🤔 Что такое шаблонные литералы, и для чего они нужны?

Были введены в ECMAScript 2015 (ES6) и представляют собой улучшение стандартных строк в JavaScript. Они облегчают работу с строками, предоставляя более удобный синтаксис для создания многострочных строк, интерполяции переменных и встроенных выражений.

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

🟠Интерполяция переменных и выражений
Шаблонные литералы позволяют встраивать переменные и выражения внутрь строк с помощью синтаксиса ${}.
const name = 'Alice';
const age = 25;

const greeting = `Hello, my name is ${name} and I am ${age} years old.`;

console.log(greeting);
// Вывод: Hello, my name is Alice and I am 25 years old.


🟠Многострочные строки
Шаблонные литералы позволяют создавать многострочные строки без необходимости использования символов переноса строки (\n).
const multiLineString = `This is a string
that spans across
multiple lines.`;

console.log(multiLineString);
// Вывод:
// This is a string
// that spans across
// multiple lines.


🟠Встроенные выражения
Внутри шаблонных литералов можно использовать любые JavaScript-выражения.
const a = 5;
const b = 10;

const result = `The sum of ${a} and ${b} is ${a + b}.`;

console.log(result);
// Вывод: The sum of 5 and 10 is 15.


🟠Вызов функций внутри шаблонных литералов
Можно вызывать функции и методы внутри шаблонных литералов.
function toUpperCase(str) {
return str.toUpperCase();
}

const name = 'Alice';

const loudGreeting = `Hello, ${toUpperCase(name)}!`;

console.log(loudGreeting);
// Вывод: Hello, ALICE!


🟠Тегированные шаблонные литералы
Тегированные шаблонные литералы позволяют обработать строку с помощью функции перед ее окончательной интерпретацией.
function tag(strings, ...values) {
console.log(strings);
console.log(values);
return 'Tagged template';
}

const name = 'Alice';
const age = 25;

const taggedResult = tag`Name: ${name}, Age: ${age}`;

console.log(taggedResult);
// Вывод:
// [ 'Name: ', ', Age: ', '' ]
// [ 'Alice', 25 ]
// Tagged template


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
🤔 Зачем переписывать легаси на новые технологии?

- Легаси-технологии могут:
- не поддерживаться;
- быть уязвимыми;
- тормозить развитие и найм.
- Новые технологии:
- упрощают поддержку;
- ускоряют разработку;
- позволяют внедрять CI/CD, тестирование, масштабирование.
Важно: не переписывать ради переписывания, а по конкретной цели и этапам


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊7👍3🔥1
🤔 Чем em отличается от rem?

🟠Как работает `em`?
em зависит от размера шрифта родителя (font-size).
.parent {
font-size: 20px;
}

.child {
font-size: 1.5em; /* 1.5 * 20px = 30px */
}


Но если .child вложен в .parent, то он наследует font-size, а его em вычисляется относительно родителя.
.parent {
font-size: 20px;
}

.child {
font-size: 1.5em; /* 30px */
}

.grandchild {
font-size: 2em; /* 2 * 30px = 60px */
}


🟠Как работает `rem`?
rem всегда зависит от font-size у <html>.
html {
font-size: 16px;
}

.container {
font-size: 2rem; /* 2 * 16px = 32px */
}


🟠Когда использовать `em`, а когда `rem`?
Используйте rem, если нужно, чтобы шрифты и размеры были предсказуемыми
Используйте em, если хотите, чтобы элементы зависели от родителя

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥1
🤔 Как бы эмулировал поведение position: sticky?

С помощью JavaScript, отслеживая скролл (scroll), и вручную добавляя/удаляя классы position: fixed и top в зависимости от позиции элемента в viewport. Это нужно, если браузер не поддерживает sticky или требуется кросс-браузерная совместимость.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
🤔 Что такое макро и микро задачи?

Макро- и микро-задачи являются частью механизма асинхронного программирования и внутренней модели выполнения, которые помогают управлять порядком выполнения асинхронных операций. Эти понятия связаны с циклом событий (event loop) и очередью задач. Давайте разберемся, что они собой представляют и как работают.

🚩Микро-задачи (Microtasks)

Это задачи, которые запланированы для выполнения сразу после текущего выполненного скрипта, но до того, как event loop продолжит обрабатывать следующую макро-задачу.
then, catch, finally
queueMicrotask()

🚩Макро-задачи (Macrotasks)

Это задачи, которые обрабатываются event loop, каждая макро-задача извлекается из очереди и выполняется до конца, прежде чем event loop перейдет к следующей макро-задаче.
setTimeout
setInterval
setImmediate (Node.js)
I/O операции
Интерактивные действия, такие как клики мыши и нажатия клавиш

🚩Различия и порядок выполнения

Основное различие между макро- и микро-задачами заключается в их приоритете выполнения:
После каждой макро-задачи, перед тем как переходить к следующей макро-задаче, event loop обрабатывает все микро-задачи в очереди микро-задач. Это означает, что микро-задачи выполняются чаще и имеют более высокий приоритет по сравнению с макро-задачами. Если во время выполнения микро-задач добавляются новые микро-задачи, они также будут выполнены в текущем цикле, перед переходом к следующей макро-задаче. Это может привести к "голоданию" макро-задач, если микро-задачи постоянно добавляются.
setTimeout(() => console.log('Макро-задача 1'), 0);
Promise.resolve().then(() => console.log('Микро-задача 1'));

setTimeout(() => console.log('Макро-задача 2'), 0);
Promise.resolve().then(() => console.log('Микро-задача 2'));


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🤔 Как выбрать правильные библиотеки для подключения к проекту?

Ориентируйся на:
- актуальность и поддержку;
- количество скачиваний и звёзд (на GitHub, npm);
- документацию и сообщество;
- совместимость с текущим стеком;
- размер и влияние на bundle.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥1💊1
🤔 Как проверить правильную иерархию html заголовков?

Проверить правильную иерархию HTML-заголовков важно для улучшения доступности (Accessibility) и SEO. Правильная структура заголовков помогает пользователям (включая тех, кто использует скринридеры) и поисковым системам лучше понимать содержание страницы.

🚩Зачем нужна правильная иерархия заголовков?

🟠Логическая структура
Заголовки задают структуру страницы, разбивая контент на разделы и подразделы. Это как оглавление книги.
🟠Доступность
Люди, использующие вспомогательные технологии (например, скринридеры), полагаются на правильные заголовки для навигации по странице.
🟠SEO
Поисковые системы оценивают структуру заголовков для индексации и понимания ключевых тем страницы.

🚩Что значит "правильная иерархия заголовков"?

🟠`<h1>`
Заголовок страницы (должен быть уникальным и только один на странице).
🟠`<h2>`
Подразделы <h1>.
🟠`<h3>`
Подразделы <h2>, и так далее.

<h1>Главный заголовок страницы</h1>
<h2>Раздел 1</h2>
<h3>Подраздел 1.1</h3>
<h3>Подраздел 1.2</h3>
<h2>Раздел 2</h2>
<h3>Подраздел 2.1</h3>
<h4>Подраздел 2.1.1</h4>


🚩Как проверить иерархию заголовков?

🟠Ручная проверка HTML-кода
Вручную просмотрите HTML-код страницы и убедитесь, что заголовки идут в порядке. Например, <h1><h2><h3> и так далее, без "перескакивания". Избегайте "пропуска уровней" (например, от <h2> сразу к <h4>).

🟠Использование браузерных DevTools
В браузере откройте DevTools (например, в Chrome нажмите F12 или Ctrl+Shift+I). Перейдите на вкладку "Elements" (Элементы). Найдите заголовки (<h1>, <h2> и так далее) и проверьте их последовательность.

🟠Инструменты для проверки доступности
Используйте расширения или инструменты для оценки доступности, такие как: Lighthouse (встроено в Chrome DevTools). Выполните аудит доступности и посмотрите рекомендации. WAVE (Web Accessibility Evaluation Tool) — онлайн-инструмент для анализа доступности. Эти инструменты покажут ошибки или пропуски в структуре заголовков.

🟠Скрипт для автоматической проверки
Если вы работаете над большим проектом, можно написать скрипт для проверки иерархии заголовков.
const headings = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')];
headings.forEach((heading, index) => {
console.log(`${index + 1}: ${heading.tagName} - ${heading.textContent.trim()}`);
});


🟠Плагин для анализа заголовков
Для популярных CMS (например, WordPress) существуют плагины, которые проверяют структуру заголовков, например, Yoast SEO.

🚩Примеры ошибок в структуре и как их исправить

Ошибка: Пропуск уровней заголовков
<h1>Главный заголовок</h1>
<h3>Подраздел</h3> <!-- Пропущен <h2> -->


Исправление
<h1>Главный заголовок</h1>
<h2>Подраздел</h2>


Ошибка: Несколько <h1> на одной странице
<h1>Главный заголовок</h1>
<h1>Еще один заголовок</h1>


Исправление
<h1>Главный заголовок</h1>
<h2>Еще один заголовок</h2>


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🤔 Что такое adaptive и responsive web-страницы?

- Responsive (отзывчивый) дизайн — подстраивается под любое устройство с помощью гибкой сетки, процентов, vw/vh, media-запросов.
- Adaptive (адаптивный) — использует фиксированные "точки останова", под каждое разрешение загружается заранее подготовленная верстка.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2