12 популярных вопросов с ответами на JavaScript собеседовании
2 года назад·4 мин. на чтение
Эта статья предназначена для junior разработчиков, которые хотят получить свою первую работу в качестве JavaScript разработчиков.
1. Что такое JavaScript и для чего он используется?
Javascript - это высокоуровневый, динамичный и интерпретируемый язык программирования, широко используемый для многих сред, таких как интерфейс веб-сайтов, backend службы, desktop и мобильные приложения. Но чаще всего JavaScript используется для создания динамических и интерактивных веб-сайтов.2. Что такое замыкание в JavaScript?
Замыкание — это функция, которая имеет доступ к переменным своей внешней функции. Это важно, потому что это позволяет внутренней функции «запоминать» переменные из своей внешней области, поэтому она может продолжать получать к ним доступ и манипулировать ими даже после того, как внешняя функция была завершена.function outerFunction(x) { return function innerFunction(y) { return x + y; }; } const add5 = outerFunction(5); console.log(add5(3)); // 8
3. Как this
работает в JavaScript?
this
важное ключевое слово в JavaScript. Его значение определяется тем, как вызывается функция. Его можно задать явно с помощью call()
, apply()
или bind()
.
По умолчанию его значение внутри функции устанавливается в глобальный объект (в браузере это объект window
), за исключением того, когда функция вызывается как метод объекта.
В этом случае this
указывает на объект, методом которого он является.
4. Что такое поднятие (hoisting) в JavaScript?
В JavaScript поднятие — это поведение, при котором переменные и объявления функций в памяти автоматически перемещаются в верхнюю часть своей области. Что это значит? Ну, это означает, что эти переменные могут быть использованы до того, как они будут объявлены, и объявления функций также могут быть вызваны до их определения.5. В чем разница между var
, let
и const?
var
: Переменные, объявленные с помощьюvar
, имеют функциональную область действия, что означает, что к ним можно получить доступ только в рамках функции, в которой они были объявлены. Внешняя переменная затеняется, когда переменная с тем же именем объявляется во вложенной функции.let
: Пусть переменные имеют блочную область действия, что означает, что они доступны только в том блоке, в котором они объявлены. Объявление переменной с тем же именем во вложенном блоке приведет к затенению внешней переменной.const
: переменные, объявленные с помощьюconst
, также какlet
, имеют область действия блока, но их нельзя переназначить.
function example() { var x = 1; let y = 2; const z = 3; if (true) { var x = 4; let y = 5; const z = 6; } console.log(x); // 4 console.log(y); // 2 console.log(z); // 3 } example();
6. Какова разница между ==
и ===
?
Оператор равенства ==
выполняет принудительное приведение типа, что означает, что он попытается преобразовать операнды в один и тот же тип перед их сравнением. С другой стороны, оператор строгого равенства ===
, не выполняет приведение типа. Он возвращает значение true
только в том случае, если оба операнда имеют одинаковый тип и значение.
Например:
console.log(1 == '1'); // true console.log(1 === '1'); // false
7. В чем разница между null
и undefined
?
undefined
указывает, что переменная была объявлена, но ей не было присвоено значение. Значение null
представляет собой преднамеренное отсутствие какого-либо значения объекта. Другими словами, null
— это явно заданное значение, указывающее на отсутствие значения.
8. Что такое событие в JavaScript?
В JavaScript событие — это любое действие в браузере, например нажатие пользователем кнопки, загрузка страницы или обновленный элемент. Слушатели событий обычно обрабатывают события, позволяя разработчикам указывать функции, которые должны выполняться при возникновении события. Пример:const button = document.querySelector('button'); button.addEventListener('click', () => { console.log('Button was clicked!'); });
9. В чем разница между синхронным и асинхронным кодом в JavaScript?
Синхронный код выполняется блокирующим образом, что означает, что следующая строка кода будет выполнена после завершения текущей строки. С другой стороны, асинхронный код выполняется неблокирующим образом, что означает, что другой код может выполняться не ожидая завершения асинхронного кодом. Асинхронный код обычно реализуется в JavaScript с помощью обратных вызовов или Promises.10. Что такое промисы (Promise) в JavaScript?
Объект Promise — это объект, представляющий успешное или неудачное завершение асинхронной операции. Promise позволяет регистрировать обратные вызовы для получения уведомлений о завершении или сбое асинхронной операции, а также обрабатывать ошибки более удобно и централизованно. Например:const fetchData = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Data fetched!'); }, 1000); }); }; fetchData() .then(data => { console.log(data); }) .catch(error => { console.error(error); });
11. Что такое обратный вызов в JavaScript?
Функция обратного вызова передается в качестве аргумента другой функции и выполняется после завершения внешней функции. Разработчики могут использовать обратные вызовы для указания кода, который будет выполняться после завершения асинхронной операции. Пример:const fetchData = (callback) => { setTimeout(() => { callback('Data fetched!'); }, 1000); }; fetchData(data => { console.log(data); });
12. Что такое AJAX в JavaScript?
AJAX (асинхронный JavaScript и XML) — это метод выполнения асинхронных серверных запросов с веб-страницы без перезагрузки всей страницы. AJAX позволяет разработчикам динамически обновлять веб-страницу новыми данными без необходимости обновления страницы. Это достигается путем отправки HTTP-запроса из браузера на сервер и обновления только тех частей страницы, которые требуют его с ответом. Это улучшает взаимодействие с пользователем, поскольку страница не перезагружается, а обновляются только необходимые данные. Пример:Еще больше вопросов с собеседований можно найти здесь.const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://api.example.com/data'); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { console.log(xhr.responseText); } }; xhr.send();
Что такое функтор? Функциональное программирование
2 года назад·5 мин. на чтение
В этой статье на простых и доступных примерах рассмотрим одну из концепций функционального программирования - Функтор.
Это серия статей о функциональном программировании:
Функтор
Как мы обсуждали ранее, нам нужна только обертка, которая абстрагируется от обработки данных. Итак, роль функтора Имплементация функтора
Как использовать функтор
С валидными данными:
Функтор Решение задачи с функтором
Комментарии к решению с функтором
Мы смогли улучшить традиционное решение при помощи функтора
- Парадигмы программирования
- Композиция
- Функторы (рассматривается в этой статье)
- Каррирование
- Чистые функции
- Функции первого класса
Что такое функтор?
Функтор (functor) это:- обертка над значением,
- предоставляет интерфейс для преобразование (map),
- подчиняется законам функтора (поговорим о них позже).
Примеры функторов
- Массив (
Array
), - Промис (
Promise
).
Почему массив - функтор?
Вспомним определение функтора:- обертка над списком значений,
- предоставляет интерфейс для преобразования - метод
map
, - подчиняется законам функтора.
[1, 2, 3] // обернутое значение .map( // интерфейс для преобразования значения x => x * 2 )
Почему промис - функтор?
Промис это:- обертка над любым значением из JavaSctipt типов,
- предоставляет интерфейс для преобразования - метод
then
, - подчиняется законам функтора.
const promise = new Promise((resolve, reject) => { resolve( { data: "value" } // обернутое значение, в данном случае объект ) }); promise .then( // интерфейс для преобразования значения response => console.log(response) );
Что объединяет массив (или промис) и функтор?
Функтор - это паттерн проектирования, аArray
и Promise
- типы данных, которые основаны на этом паттерне.
Почему мы говорим, что массив и промис - функторы?
Чтобы понять, что функторы ближе чем кажутся. Массив и промис легко понять, при это они являются мощной концепцией. Мы используем их ежедневно, даже не подозревая об их сущности.Где использовать функторы?
Немного поговорив о функторах и связав их с нашим повседневным использованием, было бы разумно рассмотреть их подробнее. Чтобы лучше понять идею функтора, создадим свои собственные функторы. Для начала рассмотрим такую задачу. Предположим, есть следующий кусочек данных.{ products: [ { name: "All about JavaScript", type: "book", price: 22, discount: 20 } ] }
Постановка задачи
Найти финальную цену первого товара с учетом скидки. Если по какой-либо причине будут переданы неправильные данные, вывести строку "No data".Шаги алгоритма
- Найти первый продукт со скидкой,
- Применить скидку,
- Продолжать проверку данных на валидность. Если данные не валидны - вернуть "No data".
Традиционное решение
const isProductWithDiscount = product => { return !isNaN(product.discount) } const findFirstDiscounted = products => { products.find(isProductWithDiscount) } const calcPriceAfterDiscount = product => { return product.price - product.discount } const findFinalPrice = (data, fallbackValue) => { if(!data || !data.products) return fallbackValue const discountedProduct = findFirstDiscounted(data.products) if(!discountedProduct) return fallbackValue return calcPriceAfterDiscount(discountedProduct) } findFinalPrice(data, "No data")
Комментарии к традиционному решению
Достоинства:- Атомарные логические единицы (
isProductWithDiscount
,findFirstDiscounted
иcalcPriceAfterDiscount
), - Логику защищена от невалидных данных.
- Cлишком много защитных проверок. (Защитное программирование (Defensive programming) является обязательным в любом отказоустойчивом программном обеспечении. Однако, в нашем коде 50% тела функции
findFinalPrice
— проверка на валидность данных. Это слишком много). fallbackValue
почти везде.
Почему нас волнуют эти улучшения?
Потому что данный код заставляет слишком в него вникать. Это негативно влияет на DX (Developer Experience) - уровень удовлетворенности разработчика от работы с кодом. Проанализируем код, чтобы прийти к лучшему решению. Части, которые мы стремимся улучшить, формируют паттерны (защита (defence) и откат (fallback)). Хорошо то, что эти части на самом деле цельные и атомарные. Мы должны иметь возможность абстрагировать этот паттерн в оболочку, которая могла бы обрабатывать эти крайние случаи вместо нас. Обертка позаботится о крайних случаях, а нам останется позаботиться только о бизнес-логике.Функтор Maybe
Как мы обсуждали ранее, нам нужна только обертка, которая абстрагируется от обработки данных. Итак, роль функтора Maybe
состоит в том, чтобы обернуть наши данные (потенциально невалидные данные) и обработать для нас крайние случаи.
Имплементация функтора Maybe
function Maybe(value) { const isNothing = () => { return value === null || value === undefined } const map = (fn) => { return isNothing() ? Maybe() : Maybe(fn(value)) } const getValueOrFallback = { return (fallbackValue) => isNothing() ? fallbackValue : value; } return { map, getValueOrFallback, }; }
Пояснения к имплементации
isNothing
проверяет валидно ли обернутое в функторMaybe
значениеmap
- интерфейс для преобразования обернутого значения, с помощью которого мы применяем функции с бизнес логикой к обернутому значению.map
возвращает новое значение в другом экземпляреMaybe
. Таким образом, мы можем сделать цепочку вызововmap
-.map().map().map...
.getValueOrFallback
возвращает обернутое значение или запасное значениеfallbackValue
.
Как использовать функтор Maybe
?
С валидными данными:
С невалидными данными:Maybe('Hello') .map(x => x.substring(1)) .getValueOrFallback('fallback') // 'ello'
Maybe(null) .map(x => x.substring(1)) // функция не будет запущена .getValueOrFallback('fallback') // 'fallback'
Maybe
обработал крайние случаи вместо нас и не запустил функцию с невалидными данными. Нам нужно лишь позаботиться о бизнес логике. Таким образом, мы внедрили улучшение, о котором говорили в традиционном решении. Внедрим это решение в задачу.
Решение задачи с функтором Maybe
const isProductWithDiscount = product => { return !isNaN(product.discount) } const findFirstDiscounted = products => { return products.find(isProductWithDiscount) } const calcPriceAfterDiscount = product => { return product.price - product.discount } Maybe(data) .map((x) => x.products) .map(findFirstDiscounted) .map(calcPriceAfterDiscount) .getValueOrFallback("No data")
Комментарии к решению с функтором Maybe
Мы смогли улучшить традиционное решение при помощи функтора Maybe
:
- мы не защищаем код сами, вместо нас это делает функтор
Maybe
, - мы указали
fallbackValue
только один раз.
Maybe
соответствует определению функтора? Функтор Maybe
это:
- обертка над любым значением из JavaScript типов,
- предоставляет интерфейс для преобразования - метод
map
, - подчиняется законам функтора.
Законы функторов
Закон идентичности (Identity law)
Если при выполнении операции преобразования, значения в функторе преобразовываются сами на себя, результатом будет немодифицированный функтор.const m1 = Maybe(value) const m2 = Maybe(value).map(v => v) // m1 и m2 эквивалентны
Закон композиции (Composition law)
Если две последовательные операции преобразования выполняются одна за другой с использованием двух функций, результат должен быть таким же, как и при одной операции отображения с одной функцией, что эквивалентно применению первой функции к результату второй.const m1 = Maybe(value).map(v => f(g(v))) const m2 = Maybe(value).map(v => g(v)).map(v => f(v)) // m1 и m2 эквивалентны
Зачем использовать функторы?
- Абстракция над применением функции,
- Усиление композиции функций,
- Уменьшение количества защитного кода (как в функторе
Maybe
), - Более чистая структура кода,
- Переменные более явно указывают на то, что мы ожидаем (что
Maybe
моделирует значение, которое может присутствовать, а может и не присутствовать).
Что означает Абстракция над применением функции?
То, что мы передаем функцию (т.е.x => x.products
) в интерфейс преобразования (т.е. map
) обертки (т.е. Maybe
), и она знает, как позаботиться о себе (посредством своей внутренней реализации).
Нас не интересуют детали реализации оболочки, которые она содержит (детали реализации скрыты), и тем не менее мы знаем, как использовать обертку (Array
или Promise
), используя их интерфейсы преобразования (map
).
И это на самом деле крайне важно в мире программирования. Это снижает планку того, как много мы, как программисты, должны понимать, чтобы иметь возможность что-то сделать. Функторы могут быть реализованы на любом языке, поддерживающем функции высшего порядка (а таких в наши дни большинство).
Почему функторы не используются повсеместно?
Просто потому, что мы к ним не привыкли. До.map
(и .then
) мы мутировали массивы или перебирали их значения вручную. Но как только мы обнаружили .map
, мы начали адаптировать его в качестве нового инструмента преобразования. Я надеюсь, что, поняв ценность функторов, мы начнем чаще внедрять их в наши ежедневные задачи как привычный инструмент.
Функтор Maybe
- лишь пример функтора. Существует множество функторов, которые выполняет различные задачи. В этой статье мы рассмотрели самый простой из них, чтобы понять саму идею функторов.