[Перевод] 70 вопросов по JavaScript для подготовки к собеседованию
Доброго времени суток, друзья!
Представляю Вашему вниманию перевод статьи Mark A »70 JavaScript Interview Questions».
Надеюсь, эта статья будет полезна как начинающим разработчикам, так и бывалым (я хотел сказать, опытным). Первым для изучения, вторым для повторения.
Лично мне понравилась емкость ответов и наглядность примеров.
В вопросах, которые показались мне сложнее прочих, приведены ссылки на дополнительную литературу.
Итак, поехали.
70 вопросов по JavaScript для подготовки к собеседованию
Вопросы:
1. В чем разница между null и undefined?
2. Для чего используется оператор »&&»?
3. Для чего используется оператор »||»?
4. Является ли использование унарного плюса (оператор »+») самым быстрым способом преобразования строки в число?
5. Что такое DOM?
6. Что такое распространение события (Event Propogation)?
7. Что такое всплытие события (Event Bubbling)?
8. Что такое погружение события (Event Capturing)?
9. В чем разница между методами event.preventDefault () и event.stopPropagation ()?
10. Как узнать об использовании метода event.preventDefault ()?
11. Почему obj.someprop.x приводит к ошибке?
12. Что такое цель события или целевой элемент (event.target)?
13. Что такое текущая цель события (event.currentTarget)?
14. В чем разница между операторами »==» и »===»?
15. Почему результатом сравнения двух похожих объектов является false?
16. Для чего используется оператор »!»?
17. Как записать несколько выражений в одну строку?
18. Что такое поднятие (Hoisting)?
19. Что такое область видимости (Scope)?
20. Что такое замыкание (Closures)?
21. Какие значения в JS являются ложными?
22. Как проверить, является ли значение ложным?
23. Для чего используется директива «use strict»?
24. Какое значение имеет this?
25. Что такое прототип объекта?
26. Что такое IIFE?
27. Для чего используется метод Function.prototype.apply?
28. Для чего используется метод Function.prototype.call?
29. В чем разница между методами call и apply?
30. Для чего используется метод Function.prototype.bind?
31. Что такое функциональное программирование и какие особенности JS позволяют говорить о нем как о функциональном языке программирования?
32. Что такое функции высшего порядка (Higher Order Functions)?
33. Почему функции в JS называют объектами первого класса (First-class Objects)?
34. Как бы Вы реализовали метод Array.prototype.map?
35. Как бы Вы реализовали метод Array.prototype.filter?
36. Как бы Вы реализовали метод Array.prototype.reduce?
37. Что такое объект arguments?
38. Как создать объект, не имеющий прототипа?
39. Почему в представленном коде переменная b становится глобальной при вызове функции?
40. Что такое ECMAScript?
41. Что нового привнес в JS стандарт ES6 или ECMAScript2015?
42. В чем разница между ключевыми словами «var», «let» и «const»?
43. Что такое стрелочные функции (Arrow Functions)?
44. Что такое классы (Classes)?
45. Что такое шаблонные литералы (Template Literals)?
46. Что такое деструктуризация объекта (Object Destructuring)?
47. Что такое модули (Modules)?
48. Что такое объект Set?
49. Что такое функция обратного вызова (Callback Function)?
50. Что такое промисы (Promises)?
51. Что такое async/await?
52. В чем разница между spread-оператором и rest-оператором?
53. Что такое параметры по умолчанию (Default Parameters)?
54. Что такое объектная обертка (Wrapper Objects)?
55. В чем разница между явным и неявным преобразованием или приведением к типу (Implicit and Explicit Coercion)?
56. Что такое NaN? Как проверить, является ли значение NaN?
57. Как проверить, является ли значение массивом?
58. Как проверить, что число является четным, без использования деления по модулю или деления с остатком (оператора »%»)?
59. Как определить наличие свойства в объекте?
60. Что такое AJAX?
61. Как в JS создать объект?
62. В чем разница между методами Object.freeze и Object.seal?
63. В чем разница между оператором «in» и методом hasOwnProperty?
64. Какие приемы работы с асинхронным кодом в JS Вы знаете?
65. В чем разница между обычной функцией и функциональным выражением?
66. Как в JS вызвать функцию?
67. Что такое запоминание или мемоизация (Memoization)?
68. Как бы Вы реализовали вспомогательную функцию запоминания?
69. Почему typeof null возвращает object? Как проверить, является ли значение null?
70. Для чего используется ключевое слово «new»?
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:
Document Object Model
DOM этого HTML выглядит так:
В JS DOM представлен объектом Document. Объект Document имеет большое количество методов для работы с элементами, их созданием, модификацией, удалением и т.д.
6. Что такое распространение события (Event Propagation)?
Когда какое-либо событие происходит в элементе DOM, оно на самом деле происходит не только в нем. Событие «распространяется» от объекта Window до вызвавшего его элемента (event.target). При этом событие последовательно пронизывает (затрагивает) всех предков целевого элемента. Распространение события имеет три стадии или фазы:
- Фаза погружения (захвата, перехвата) — событие возникает в объекте Window и опускается до цели события через всех ее предков.
- Целевая фаза — это когда событие достигает целевого элемента.
- Фаза всплытия — событие поднимается от event.target, последовательно проходит через всех его предков и достигает объекта Window.
Подробнее о распространении событий можно почитать здесь и здесь.
7. Что такое всплытие события?
Когда событие происходит в элементе DOM, оно затрагивает не только этот элемент. Событие «всплывает» (подобно пузырьку воздуха в воде), переходит от элемента, вызвавшего событие (event.target), к его родителю, затем поднимается еще выше, к родителю родителя элемента, пока не достигает объекта Window.
Допустим, у нас есть такая разметка:
1
И такой 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 до цели события через всех его предков.
Разметка:
1
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. Прототип несуществующего свойства объекта имеет undefined в качестве значения по умолчанию, а у undefined нет свойства x.
12. Что такое цель события или целевой элемент (event.target)?
Простыми словами, event.target — это элемент, в котором происходит событие, или элемент, вызвавший событие.
Имеем такую разметку:
И такой простенький JS:
function clickFunc(event) {
console.log(event.target)
}
Мы прикрепили «слушатель» к внешнему div. Однако если мы нажмем на кнопку, то получим в консоли разметку этой кнопки. Это позволяет сделать вывод, что элементом, вызвавшим событие, является именно кнопка, а не внешний или внутренние div.
13. Что такое текущая цель события (event.currentTarget)?
Event.currentTarget — это элемент, к которому прикреплен прослушиватель событий.
Аналогичная разметка:
И немного видоизмененный JS:
function clickFunc(event) {
console.log(event.currentTarget)
}
Мы прикрепили слушатель к внешнему div. Куда бы мы ни кликнули, будь то кнопка или один из внутренних div, в консоли мы всегда получим разметку внешнего div. Это позволяет заключить, что event.currentTarget — это элемент, к которому прикреплен прослушиватель событий.
14. В чем разница между операторами »==» и »===»?
Разница между оператором »==» (абстрактное или нестрогое равенство) и оператором »===» (строгое равенство) состоит в том, что первый сравнивает значения после их преобразования или приведения к одному типу (Coersion), а второй — без такого преобразования.
Давайте копнем глубже. И сначала поговорим о преобразовании.
Преобразование представляет собой процесс приведения значения к другому типу или, точнее, процесс приведения сравниваемых значений к одному типу. При сравнении оператор »==» производит так называемое неявное сравнение. Оператор »==» выполняет некоторые операции перед сравнением двух значений.
Допустим, мы сравниваем x и y.
Алгоритм следующий:
- Если x и y имеют одинаковый тип, сравнение выполняется с помощью оператора »===».
- Если x = null и y = undefined возвращается true.
- Если x = undefined и y = null возвращается true.
- Если x = число, а y = строка, возвращается x == toNumber (y) (значение y преобразуется в число).
- Если x = строка, а y = число, возвращается toNumber (x) == y (значение x преобразуется в число).
- Если x = логическое значение, возвращается toNumber (x) == y.
- Если y = логическое значение, возвращается x == toNumber (y).
- Если x = строка, символ или число, а y = объект, возвращается x == toPrimitive (y) (значение y преобразуется в примитив).
- Если x = объект, а y = строка, символ или число, возвращается toPrimitive (x) == y.
- Возвращается false.
Запомните: для приведения объекта к «примитиву» метод toPrimitive сначала использует метод valueOf, затем метод toString.
Примеры:
Все примеры возвращают true.
Первый пример — первое условие алгоритма.
Второй пример — четвертое условие.
Третий — второе.
Четвертый — седьмое.
Пятый — восьмое.
И последний — десятое.
Если же мы используем оператор »===» все примеры, кроме первого, вернут 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' являются ближайшими
// к внутренней области видимости переменными
20. Что такое замыкание (Closures)?
Наверное, это самый сложный вопрос из списка. Я постараюсь объяснить, как я понимаю замыкание.
По сути, замыкание — это способность функции во время создания запоминать ссылки на переменные и параметры, находящиеся в текущей области видимости, в области видимости родительской функции, в области видимости родителя родительской функции и так до глобальной области видимости с помощью цепочки областей видимости. Обычно область видимости определяется при создании функции.
Примеры — отличный способ объяснить замыкание:
// глобальная область видимости
var globalVar = 'abc'
function a() {
// область видимости функции
console.log(globalVar)
}
a() // 'abc'
// цепочка областей видимости
// область видимости функции a -> глобальная область видимости
В данном примере, когда мы объявляем функцию, глобальная область видимости является частью замыкания.
Переменная «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')
В результате получаем «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».
Подробнее о замыкании можно почитать здесь и здесь.
21. Какие значения в JS являются ложными?
const falsyValues = ['', 0, null, undefined, NaN, false]
Ложными являются значения, результатом преобразования которых в логическое значение является false.
22. Как проверить, является ли значение ложным?
Следует использовать функцию Boolean или оператор »!» (двойное отрицание).
23. Для чего используется директива «use strict»?
«use strict» — это директива ES5, которая заставляет весь наш код или код отдельной функции выполняться в строгом режиме. Строгий режим вводит некоторые ограничения по написанию кода, тем самым позволяя избегать ошибок на ранних этапах.
Вот какие ограничения накладывает строгий режим.
Нельзя присваивать значения или обращаться к необъявленным переменным:
function returnY() {
'use strict'
y = 123
return y
}
returnY() // Uncaught ReferenceError: y is not defined
Запрещено присваивать значения глобальный переменным, доступным только для чтения или записи:
'use strict'
var NaN = NaN // Uncaught TypeError: Cannot assign to read only property 'NaN' of object '#'
var undefined = undefined
var Infinity = 'and beyond'
Нельзя удалить «неудаляемое» свойство объекта:
'use strict'
const obj = {}
Object.defineProperties(obj, 'x', {
value: 1
})
delete obj.x // Uncaught TypeError: Property description must be an object: x
Запрещено дублирование параметров:
'use strict'
function someFunc(a, b, b, c) {} // Uncaught SyntaxError: Duplicate parameter name not allowed in this context
Нельзя создавать функции с помощью функции eval:
'use strict'
eval('var x = 1')
console.log(x) // Uncaught ReferenceError: x is not defined
Значением «this» по умолчанию является undefined:
'use strict'
function showMeThis() {
return this
}
showMeThis() // undefined
… и т.д.
24. Какое значение имеет this?
Обычно this ссылается на значение объекта, который в данный момент выполняет или вызывает функцию. «В данный момент» означает, что значение this меняется в зависимости от контекста выполнения, от того места, где мы используем this.
const carDetails = {
name: 'Ford Mustang',
yearBought: 2005,
getName() {
return this.name
}
isRegistered: true
}
console.log(carDetails.getName()) // Ford Mustang
В данном случае метод getName возвращает this.name, а this ссылается на carDetails, объект, в котором выполняется getName, который является ее «владельцем».
Добавим после console.log три строчки:
var name = 'Ford Ranger'
var getCarName = carDetails.getName
console.log(getCarName()) // Ford Ranger
Второй console.log выдает Ford Ranger, и это странно. Причина такого поведения заключается в том, что «владельцем» getCarName является объект window. Переменные, объявленные с помощью ключевого слова «var» в глобальной области видимости, записываются в свойства объекта window. this в глобальной области видимости ссылается на объект window (если речь не идет о строгом режиме).
console.log(getCarName === window.getCarName) // true
console.log(getCarName === this.getCarName) // true
В этом примере this и window ссылаются на один объект.
Одним из способов решения данной проблемы является использование методов call или apply:
console.log(getCarName.apply(carDetails)) // Ford Mustang
console.log(getCarName.call(carDetails)) // Ford Mustang
Call и apply принимают в качестве первого аргумента объект, который будет являться значением this внутри функции.
В IIFE, функциях, которые создаются в глобальном области видимости, анонимных функциях и внутренних функциях методов объекта значением this по умолчанию является объект window.
(function() {
console.log(this)
})() // window
function iHateThis() {
console.log(this)
}
iHateThis() // window
const myFavouriteObj = {
guessThis() {
function getName() {
console.log(this.name)
}
getName()
},
name: 'Marko Polo',
thisIsAnnoying(callback) {
callback()
}
}
myFavouriteObj.guessThis() // window
myFavouriteObj.thisIsAnnoying(function() {
console.log(this) // window
})
Существует два способа получить «Marko Polo».
Во-первых, мы можем сохранить значение this в переменной:
const myFavoriteObj = {
guessThis() {
const self = this // сохраняем значение this в переменной self
function getName() {
console.log(self.name)
}
getName()
},
name: 'Marko Polo',
thisIsAnnoying(callback) {
callback()
}
}
Во-вторых, мы можем использовать стрелочную функцию:
const myFavoriteObj = {
guessThis() {
const getName = () => {
// копируем значение this из внешнего окружения
console.log(this.name)
}
getName()
},
name: 'Marko Polo',
thisIsAnnoying(callback) {
callback()
}
}
Стрелочные функции не имеют собственного значения this. Они копируют значение this из внешнего лексического окружения.
25. Что такое прототип объекта?
В двух словах, прототип — это план (схема или проект) объекта. Он используется как запасной вариант для свойств и методов, существующих в данном объекте. Это также один из способов обмена свойствами и функциональностью между объектами. Это основная концепция прототипного наследования в JS.
const o = {}
console.log(o.toString()) // [object Object]
Несмотря на то, что объект «о» не имеет свойства toString, обращение к этому свойству не вызывает ошибки. Если определенного свойства нет в объекте, его поиск осуществляется сначала в прототипе объекта, затем в прототипе прототипа объекта и так до тех пор, пока свойство не будет найдено. Это называется цепочкой прототипов. На вершине цепочки прототипов находится Object.prototype.
console.log(o.toString === Object.prototype.toString) // true
Подробнее о прототипах и наследовании можно почитать здесь и здесь.
26. Что такое IIFE?
IIFE или Immediately Invoked Function Expression — это функция, которая вызывается или выполняется сразу же после создания или объявления. Для создания IIFE необходимо обернуть функцию в круглые скобки (оператор группировки), превратив ее в выражение, и затем вызвать ее с помощью еще одних круглых скобок. Это выглядит так: (function (){})().
(function( ) { }( ))
(function( ) { })( )
(function named(params) { })( )
(( ) => { })
(function(global) { })(window)
const utility = (function( ) {
return {
// утилиты
}
})
Все эти примеры являются валидными. Предпоследний пример показывает, что мы можем передавать параметры в IIFE. Последний пример показывает, что мы можем сохранить результат IIFE в переменной.
Лучшее использование IIFE — это выполнение функций настройки инициализации и предотвращение конфликтов имен с другими переменными в глобальной области видимости (загрязнение глобального пространства имен). Приведем пример.
У нас есть ссылка на библиотеку somelibrary.js, которая предоставляет некоторые глобальные функции, которые мы можем использовать в нашем коде, но в этой библиотеке есть два метода, createGraph и drawGraph, которые мы не используем, потому что они содержат ошибки. И мы хотим реализовать эти функции самостоятельно.
Одним из способов решить данную проблему является изменение структуры наших скриптов:
Таким образом, мы переопределяем методы, предоставляемые библиотекой.
Вторым способом является изменение имен наших функций:
Третий способ — использование IIFE:
В этом примере мы создаем служебную переменную, которая содержит результат IIFE, возвращающий объект, содержащий методы createGraph и drawGraph.
Вот еще одна проблема, которую можно решить с помощью IIFE:
val li = document.querySelectorAll('.list-group > li')
for (var i - 0, len = li.length; i < len; i++) {
li[i].addEventListener('click', function(e) {
console.log(i)
})
}
Допустим, у нас есть элемент «ul» с классом «list-group», содержащий 5 дочерних элементов «li». И мы хотим выводить в консоль значение «i» при клике по отдельному «li». Однако вместо этого в консоль всегда выводится 5. Виной всему замыкание.
Одним из решений является IIFE:
var li = document.querySelectorAll('.list-group > li')
for (var i = 0, len = li.length; i < len; i++) {
(function(currentIndex) {
li[currentIndex].addEventListener('click', function(e) {
console.log(currentIndex)
})
})(i)
}
Причина, по которой этот код работает, как задумано, состоит в том, что IIFE создает новую область видимости на каждой итерации, и мы записываем значение «i» в currentIndex.
27. Для чего используется метод Function.prototype.apply?
Apply используется для привязки определенного объекта к значению this вызываемой функции.
const details = {
message: 'Hello World!'
}
function getMessage() {
return this.message
}
getMessage.apply(details) // Hello World!
Этот метод похож на Function.prototype.call. Единственное отличие состоит в том, что в apply аргументы передаются в виде массива.
const person = {
name: 'Marko Polo'
}
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`
}
greeting.apply(person, ['Hello']) // Hello Marko Polo
28. Для чего используется метод Function.prototype.call?
Call используется для привязки определенного объекта к значению this вызываемой функции.
const details = {
message: 'Hello World!'
};
function getMessage() {
return this.message;
}
getMessage.call(details); // Hello World!
Этот метод похож на Function.prototype.apply. Отличие состоит в том, что в call аргументы передаются через запятую.
const person = {
name: 'Marko Polo'
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // Hello Marko Polo
29. В чем разница между методами call и apply?
Отличие между call и apply состоит в том, как мы передаем аргументы в вызываемой функции. В apply аргу