Добавить в корзинуПозвонить
Найти в Дзене
От Джуна до Лида (IT Jobs)

20 вопросов по JavaScript для подготовки к собеседованию

1. В чем разница между null и undefined? Для начала давайте поговорим о том, что у них общего. Во-первых, они принадлежат к 7 «примитивам» (примитивным типам) JS: let primitiveTypes = ['string', 'number', 'null', 'undefined', 'boolean', 'symbol', 'bigint'] Во-вторых, они являются ложными значениями, т.е. результатом их преобразования в логическое значение с помощью Boolean() или оператора "!!" является false: console.log(!!null) // false
console.log(!!undefined) // false
console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false Ладно, теперь о различиях. undefined («неопределенный») представляет собой значение по умолчанию: В указанных случаях движок JS присваивает значение undefined. let _thisIsUndefined
const doNothing = () => {}
const someObj = {
a: 'ay',
b: 'bee',
c: 'si'
}
console.log(_thisIsUndefined) // undefined
console.log(doNothing()) // undefined
console.log(someObj['d']) // undefined null — это «значение отсутствия значения». null — это значе
Оглавление

1. В чем разница между null и undefined?

Для начала давайте поговорим о том, что у них общего.

Во-первых, они принадлежат к 7 «примитивам» (примитивным типам) JS:

let primitiveTypes = ['string', 'number', 'null', 'undefined', 'boolean', 'symbol', 'bigint']

Во-вторых, они являются ложными значениями, т.е. результатом их преобразования в логическое значение с помощью Boolean() или оператора "!!" является false:

console.log(!!null) // false
console.log(!!undefined) // false

console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false

Ладно, теперь о различиях.

undefined («неопределенный») представляет собой значение по умолчанию:

  • переменной, которой не было присвоено значения, т.е. объявленной, но не инициализированной переменной;
  • функции, которая ничего не возвращает явно, например, console.log(1);
  • несуществующего свойства объекта.

В указанных случаях движок JS присваивает значение undefined.

let _thisIsUndefined
const doNothing = () => {}
const someObj = {
a: 'ay',
b: 'bee',
c: 'si'
}
console.log(_thisIsUndefined) // undefined
console.log(doNothing()) // undefined
console.log(someObj['d']) // undefined

null — это «значение отсутствия значения». null — это значение, которое присваивается переменной явно. В примере ниже мы получаем null, когда метод fs.readFile отрабатывает без ошибок:

fs.readFile('path/to/file', (e, data) => {
console.log(e) // здесь мы получаем null
if(e) {
console.log(e)
}
console.log(data)
})

При сравнении null и undefined мы получаем true, когда используем оператор "==", и false при использовании оператора "===". О том, почему так происходит, см. ниже.

console.log(null == undefined) // true
console.log(null === undefined) // false

2. Для чего используется оператор "&&"?

Оператор "&&" (логическое и) находит и возвращает первое ложное значение либо последний операнд, когда все значения истинные. Он использует короткое замыкание во избежание лишних затрат:

console.log(false && 1 && []) // false
console.log(' ' && true && 5) // 5

С оператором «if»:

const router: Router = Router()

router.get('/endpoint', (req: Request, res: Response) => {
let conMobile: PoolConnection
try {
// операции с базой данных
} catch (e) {
if (conMobile) {
conMobile.release()
}
}
})

То же самое с оператором "&&":

const router: Router = Router()

router.get('/endpoint', (req: Request, res: Response) => {
let conMobile: PoolConnection
try {
// операции с базой данных
} catch (e) {
conMobile && conMobile.release()
}
})

3. Для чего используется оператор "||"?

Оператор "||" (логическое или) находит и возвращает первое истинное значение. Он также использует короткое замыкание. Данный оператор использовался для присвоения параметров по умолчанию в функциях до того, как параметры по умолчанию были стандартизированы в ES6.

console.log(null || 1 || undefined) // 1

function logName(name) {
let n = name || Mark
console.log(n)
}

logName() // Mark

4. Является ли использование унарного плюса (оператор "+") самым быстрым способом преобразования строки в число?

Согласно MDN оператор "+" действительно является самым быстрым способом преобразования строки в число, поскольку он не выполняет никаких операций со значением, которое является числом.

5. Что такое DOM?

DOM или Document Object Model (объектная модель документа) — это прикладной программный интерфейс (API) для работы с HTML и XML документами. Когда браузер первый раз читает («парсит») HTML документ, он формирует большой объект, действительно большой объект, основанный на документе — DOM. DOM представляет собой древовидную структуру (дерево документа). DOM используется для взаимодействия и изменения самой структуры DOM или его отдельных элементов и узлов.

Допустим, у нас есть такой HTML:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document Object Model</title>
</head>

<body>
<div>
<p>
<span></span>
</p>
<label></label>
<input>
</div>
</body>

</html>

DOM этого HTML выглядит так:

В JS DOM представлен объектом Document. Объект Document имеет большое количество методов для работы с элементами, их созданием, модификацией, удалением и т.д.

6. Что такое распространение события (Event Propagation)?

Когда какое-либо событие происходит в элементе DOM, оно на самом деле происходит не только в нем. Событие «распространяется» от объекта Window до вызвавшего его элемента (event.target). При этом событие последовательно пронизывает (затрагивает) всех предков целевого элемента. Распространение события имеет три стадии или фазы:

  1. Фаза погружения (захвата, перехвата) — событие возникает в объекте Window и опускается до цели события через всех ее предков.
  2. Целевая фаза — это когда событие достигает целевого элемента.
  3. Фаза всплытия — событие поднимается от event.target, последовательно проходит через всех его предков и достигает объекта Window.
-2

Подробнее о распространении событий можно почитать здесь и здесь.

7. Что такое всплытие события?

Когда событие происходит в элементе DOM, оно затрагивает не только этот элемент. Событие «всплывает» (подобно пузырьку воздуха в воде), переходит от элемента, вызвавшего событие (event.target), к его родителю, затем поднимается еще выше, к родителю родителя элемента, пока не достигает объекта Window.

Допустим, у нас есть такая разметка:

<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>

И такой JS:

function addEvent(el, event, callback, isCapture = false) {
if (!el || !event || !callback || typeof callback !== 'function') return

if (typeof el === 'string') {
el = document.querySelector(el)
}
el.addEventListener(event, callback, isCapture)
}

addEvent(document, 'DOMContentLoaded', () => {
const child = document.querySelector('.child')
const parent = document.querySelector('.parent')
const grandparent = document.querySelector('.grandparent')

addEvent(child, 'click', function(e) {
console.log('child')
})

addEvent(parent, 'click', function(e) {
console.log('parent')
})

addEvent(grandparent, 'click', function(e) {
console.log('grandparent')
})

addEvent('html', 'click', function(e) {
console.log('html')
})

addEvent(document, 'click', function(e) {
console.log('document')
})

addEvent(window, 'click', function(e) {
console.log('window')
})
})

У метода addEventListener есть третий необязательный параметр — useCapture. Когда его значение равняется false (по умолчанию), событие начинается с фазы всплытия. Когда его значение равняется true, событие начинается с фазы погружения (для «прослушивателей» событий, прикрепленных к цели события, событие находится в целевой фазе, а не в фазах погружения или всплытия. События в целевой фазе инициируют все прослушиватели на элементе в том порядке, в котором они были зарегистрированы независимо от параметра useCapture — прим. пер.). Если мы кликнем по элементу child, в консоль будет выведено: child, parent, grandparent, html, document, window. Вот что такое всплытие события.

8. Что такое погружение события?

Когда событие происходит в элементе DOM, оно происходит не только в нем. В фазе погружения событие опускается от объекта Window до цели события через всех его предков.

Разметка:

<div class="grandparent">
<div class="parent">
<div class="child">1</div>
</div>
</div>

JS:

function addEvent(el, event, callback, isCapture = false) {
if (!el || !event || !callback || typeof callback !== 'function') return

if (typeof el === 'string') {
el = document.querySelector(el);
}
el.addEventListener(event, callback, isCapture)
}

addEvent(document, 'DOMContentLoaded', () => {
const child = document.querySelector('.child')
const parent = document.querySelector('.parent')
const grandparent = document.querySelector('.grandparent')

addEvent(child, 'click', function(e) {
console.log('child');
}, true)

addEvent(parent, 'click', function(e) {
console.log('parent')
}, true)

addEvent(grandparent, 'click', function(e) {
console.log('grandparent')
}, true)

addEvent('html', 'click', function(e) {
console.log('html')
}, true)

addEvent(document, 'click', function(e) {
console.log('document')
}, true)

addEvent(window, 'click', function(e) {
console.log('window')
}, true)
})

У метода addEventListener есть третий необязательный параметр — useCapture. Когда его значение равняется false (по умолчанию), событие начинается с фазы всплытия. Когда его значение равняется true, событие начинается с фазы погружения. Если мы кликнем по элементу child, то увидим в консоли следующее: window, document, html, grandparent, parent, child. Это и есть погружение события.

9. В чем разница между методами event.preventDefault() и event.stopPropagation()?

Метод event.preventDefault() отключает поведение элемента по умолчанию. Если использовать этот метод в элементе form, то он предотвратит отправку формы (submit). Если использовать его в contextmenu, то контекстное меню будет отключено (данный метод часто используется в keydown для переопределения клавиатуры, например, при создании музыкального/видео плеера или текстового редактора — прим. пер.). Метод event.stopPropagation() отключает распространение события (его всплытие или погружение).

10. Как узнать об использовании метода event.preventDefault()?

Для этого мы можем использовать свойство event.defaulPrevented, возвращающее логическое значение, служащее индикатором применения к элементу метода event.preventDefault.

11. Почему obj.someprop.x приводит к ошибке?

const obj = {}
console.log(obj.someprop.x)

Ответ очевиден: мы пытается получить доступ к свойству x свойства someprop, которое имеет значение undefined. obj.__proto__.__proto = null, поэтому возвращается undefined, а у undefined нет свойства x.

12. Что такое цель события или целевой элемент (event.target)?

Простыми словами, event.target — это элемент, в котором происходит событие, или элемент, вызвавший событие.

Имеем такую разметку:

<div onclick="clickFunc(event)" style="text-align: center; margin: 15px;
border: 1px solid red; border-radius: 3px;">
<div style="margin: 25px; border: 1px solid royalblue; border-radius: 3px;">
<div style="margin: 25px; border: 1px solid skyblue; border-radius: 3px;">
<button style="margin: 10px">
Button
</button>
</div>
</div>
</div>

И такой простенький JS:

function clickFunc(event) {
console.log(event.target)
}

Мы прикрепили «слушатель» к внешнему div. Однако если мы нажмем на кнопку, то получим в консоли разметку этой кнопки. Это позволяет сделать вывод, что элементом, вызвавшим событие, является именно кнопка, а не внешний или внутренние div.

13. Что такое текущая цель события (event.currentTarget)?

Event.currentTarget — это элемент, к которому прикреплен прослушиватель событий.

Аналогичная разметка:

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
<div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
<div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
<button style="margin:10px">
Button
</button>
</div>
</div>
</div>

И немного видоизмененный JS:

function clickFunc(event) {
console.log(event.currentTarget)
}

Мы прикрепили слушатель к внешнему div. Куда бы мы ни кликнули, будь то кнопка или один из внутренних div, в консоли мы всегда получим разметку внешнего div. Это позволяет заключить, что event.currentTarget — это элемент, к которому прикреплен прослушиватель событий.

14. В чем разница между операторами "==" и "==="?

Разница между оператором "==" (абстрактное или нестрогое равенство) и оператором "===" (строгое равенство) состоит в том, что первый сравнивает значения после их преобразования или приведения к одному типу (Coersion), а второй — без такого преобразования.

Давайте копнем глубже. И сначала поговорим о преобразовании.

Преобразование представляет собой процесс приведения значения к другому типу или, точнее, процесс приведения сравниваемых значений к одному типу. При сравнении оператор "==" производит так называемое неявное сравнение. Оператор "==" выполняет некоторые операции перед сравнением двух значений.

Допустим, мы сравниваем x и y.

Алгоритм следующий:

  1. Если x и y имеют одинаковый тип, сравнение выполняется с помощью оператора "===".
  2. Если x = null и y = undefined возвращается true.
  3. Если x = undefined и y = null возвращается true.
  4. Если x = число, а y = строка, возвращается x == toNumber(y) (значение y преобразуется в число).
  5. Если x = строка, а y = число, возвращается toNumber(x) == y (значение x преобразуется в число).
  6. Если x = логическое значение, возвращается toNumber(x) == y.
  7. Если y = логическое значение, возвращается x == toNumber(y).
  8. Если x = строка, символ или число, а y = объект, возвращается x == toPrimitive(y) (значение y преобразуется в примитив).
  9. Если x = объект, а y = строка, символ или число, возвращается toPrimitive(x) == y.
  10. Возвращается false.

Запомните: для приведения объекта к «примитиву» метод toPrimitive сначала использует метод valueOf, затем метод toString.

Примеры:

-3

Все примеры возвращают true.

Первый пример — первое условие алгоритма.

Второй пример — четвертое условие.

Третий — второе.

Четвертый — седьмое.

Пятый — восьмое.

И последний — десятое.

-4

Если же мы используем оператор "===" все примеры, кроме первого, вернут false, поскольку значения в этих примерах имеют разные типы.

15. Почему результатом сравнения двух похожих объектов является false?

let a = {
a: 1
}
let b = {
a: 1
}
let c = a

console.log(a === b) // false
console.log(a === c) // true хм...

В JS объекты и примитивы сравниваются по-разному. Примитивы сравниваются по значению. Объекты — по ссылке или адресу в памяти, где хранится переменная. Вот почему первый console.log возвращает false, а второй — true. Переменные «a» и «c» ссылаются на один объект, а переменные «a» и «b» — на разные объекты с одинаковыми свойствами и значениями.

16. Для чего используется оператор "!!"?

Оператор "!!" (двойное отрицание) приводит значение справа от него к логическому значению.

console.log(!!null) // false
console.log(!!undefined) // false
console.log(!!'') // false
console.log(!!0) // false
console.log(!!NaN) // false
console.log(!!' ') // true
console.log(!!{}) // true
console.log(!![]) // true
console.log(!!1) // true
console.log(!![].length) // false

17. Как записать несколько выражений в одну строку?

Для этого мы можем использовать оператор "," (запятая). Этот оператор «двигается» слева направо и возвращает значение последнего выражения или операнда.

let x = 5

x = (x++, x = addFive(x), x *= 2, x -= 5, x += 10)

function addFive(num) {
return num + 5
}

Если мы выведем значение x в консоль, то получим 27. Сначала мы увеличиваем значение x на единицу (x = 6). Затем вызываем функцию addFive() с параметром 6, к которому прибавляем 5 (x = 11). После этого мы умножаем значение x на 2 (x = 22). Затем вычитаем 5 (x = 17). И, наконец, прибавляем 10 (x = 27).

18. Что такое поднятие (Hoisting)?

Поднятие — это термин, описывающий подъем переменной или функции в глобальную или функциональную области видимости.

Для того, чтобы понять, что такое Hoisting, необходимо разобраться с тем, что представляет собой контекст выполнения.

Контекст выполнения — это среда, в которой выполняется код. Контекст выполнения имеет две фазы — компиляция и собственно выполнение.

Компиляция. В этой фазе функциональные выражения и переменные, объявленные с помощью ключевого слова «var», со значением undefined поднимаются в самый верх глобальной (или функциональной) области видимости (как бы перемещаются в начало нашего кода. Это объясняет, почему мы можем вызывать функции до их объявления — прим. пер.).

Выполнение. В этой фазе переменным присваиваются значения, а функции (или методы объектов) вызываются или выполняются.

Запомните: поднимаются только функциональные выражения и переменные, объявленные с помощью ключевого слова «var». Обычные функции и стрелочные функции, а также переменные, объявленные с помощью ключевых слов «let» и «const» не поднимаются.

Предположим, что у нас есть такой код:

console.log(y)
y = 1
console.log(y)
console.log(greet('Mark'))

function greet(name) {
return 'Hello ' + name + '!'
}

var y

Получаем undefined, 1 и 'Hello Mark!'.

Вот как выглядит фаза компиляции:

function greet(name) {
return 'Hello ' + name + '!'
}

var y // присваивается undefined

// ожидается завершение фазы компиляции

// затем начинается фаза выполнения
/*
console.log(y)
y = 1
console.log(y)
console.log(greet('Mark'))
*/

После завершения фазы компиляции начинается фаза выполнения, когда переменным присваиваются значения и вызываются функции.

Дополнительно о Hoisting можно почитать здесь.

19. Что такое область видимости (Scope)?

Область видимости — это место, где (или откуда) мы имеем доступ к переменным или функциям. JS имеем три типа областей видимости: глобальная, функциональная и блочная (ES6).

Глобальная область видимости — переменные и функции, объявленные в глобальном пространстве имен, имеют глобальную область видимости и доступны из любого места в коде.

// глобальное пространство имен
var g = 'global'

function globalFunc() {
function innerFunc() {
console.log(g) // имеет доступ к переменной g, поскольку она является глобальной
}
innerFunc()
}

Функциональная область видимости (область видимости функции) — переменные, функции и параметры, объявленные внутри функции, доступны только внутри этой функции.

function myFavouriteFunc(a) {
if (true) {
var b = 'Hello ' + a
}
return b
}
myFavouriteFunc('World')

console.log(a) // Uncaught ReferenceError: a is not defined
console.log(b) // не выполнится

Блочная область видимости — переменные (объявленные с помощью ключевых слов «let» и «const») внутри блока ({ }), доступны только внутри него.

function testBlock() {
if (true) {
let z = 5
}
return z
}

testBlock() // Uncaught ReferenceError: z is not defined

Область видимости — это также набор правил, по которым осуществляется поиск переменной. Если переменной не существует в текущей области видимости, ее поиск производится выше, во внешней по отношению к текущей области видимости. Если и во внешней области видимости переменная отсутствует, ее поиск продолжается вплоть до глобальной области видимости. Если в глобальной области видимости переменная обнаружена, поиск прекращается, если нет — выбрасывается исключение. Поиск осуществляется по ближайшим к текущей областям видимости и останавливается с нахождением переменной. Это называется цепочкой областей видимости (Scope Chain).

// цепочка областей видимости
// внутренняя область видимости -> внешняя область видимости -> глобальная область видимости

// глобальная область видимости
var variable1 = 'Comrades'
var variable2 = 'Sayonara'

function outer() {
// внешняя область видимости
var variable1 = 'World'

function inner() {
// внутренняя область видимости
var variable2 = 'Hello'
console.log(variable2 + ' ' + variable1)
}
inner()
}
outer()
// в консоль выводится 'Hello World',
// потому что variable2 = 'Hello' и variable1 = 'World' являются ближайшими
// к внутренней области видимости переменными

-5

20. Что такое замыкание (Closures)?

Наверное, это самый сложный вопрос из списка. Я постараюсь объяснить, как я понимаю замыкание.

По сути, замыкание — это способность функции во время создания запоминать ссылки на переменные и параметры, находящиеся в текущей области видимости, в области видимости родительской функции, в области видимости родителя родительской функции и так до глобальной области видимости с помощью цепочки областей видимости. Обычно область видимости определяется при создании функции.

Примеры — отличный способ объяснить замыкание:

// глобальная область видимости
var globalVar = 'abc'

function a() {
// область видимости функции
console.log(globalVar)
}

a() // 'abc'
// цепочка областей видимости
// область видимости функции a -> глобальная область видимости

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

-6

Переменная «globalVar» не имеет значения на картинке, потому что ее значение может меняться в зависимости от того, где и когда будет вызвана функция. Но в примере выше globalVar будет иметь значение «abc».

Теперь пример посложнее:

var globalVar = 'global'
var outerVar = 'outer'

function outerFunc(outerParam) {
function innerFunc(innerParam) {
console.log(globalVar, outerParam, innerParam)
}
return innerFunc
}

const x = outerFunc(outerVar)
outerVar = 'outer-2'
globalVar = 'guess'
x('inner')

-7

В результате получаем «guess outer inner». Объяснение следующее: когда мы вызываем функцию outerFunc и присваиваем переменной «x» значение, возвращаемое функцией innerFunc, параметр «outerParam» равняется «outer». Несмотря на то, что мы присвоили переменной «outerVar» значение «outer-2», это произошло после вызова функции outerFunc, которая «успела» найти значение переменной «outerVar» в цепочке областей видимости, этим значением было «outer». Когда мы вызываем «x», которая ссылается на innerFunc, значением «innerParam» является «inner», потому что мы передаем это значение в качестве параметра при вызове «x». globalVar имеет значение «guess», потому что мы присвоили ей это значение перед вызовом «x».

Пример неправильного понимания замыкания.

const arrFunc = []
for (var i = 0; i < 5; i++) {
arrFunc.push(function() {
return i
})
}
console.log(i) // 5

for (let i = 0; i < arrFunc.length; i++) {
console.log(arrFunc[i]()) // все 5
}

Данный код работает не так, как ожидается. Объявление переменной с помощью ключевого слова «var» делает эту переменную глобальной. После добавления функций в массив «arrFunc» значением глобальной переменной «i» становится «5». Поэтому когда мы вызываем функцию, она возвращает значение глобальной переменной «i». Замыкание хранит ссылку на переменную, а не на ее значение во время создания. Эту проблему можно решить, используя IIFE или объявив переменную с помощью ключевого слова «let».