JavaScript хорошо известен своим, казалось бы, странным поведением.
Когда я пытаюсь объяснить коллегам-разработчикам, которые не слишком хорошо знакомы с JavaScript, что в нем такого странного, мне всегда приходит на ум это веселое видео.

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

Я представлю 5 лучших концепций, которые, по моему мнению, нужно обязательно ОСНОВАТЬ, чтобы преуспеть в каждом интервью по JavaScript. Они, скорее всего, улучшат то, как вы разрабатываете и реализуете свои приложения JavaScript с точки зрения производительности и стабильности.

Давайте прыгать прямо в!

Закрытие

Я решил начать с закрытия (ха!), потому что это имеет решающее значение, и поэтому — почти всегда интервьюер попытается проверить ваши навыки, когда дело доходит до закрытия.

Итак, что такое замыкание?

«Замыкание — это комбинация функции, связанной вместе (заключенной) со ссылками на ее окружающее состояние (лексическое окружение)».

По сути, когда создается замыкание, оно «блокирует» с ним ссылки на все, на что ссылались, даже если это что-то присутствовало в другой области видимости, и даже если этой области больше нет на месте (например, функция, которая завершила выполнение ).

Давайте посмотрим на пример:

В этом примере функция outer получает параметр food, помещает его в переменную suggestion и возвращает функцию (я назвала ее для вашего удобства, это не обязательно).
Возвращенная функция получает параметр name, и использует его в строке для вывода на экран. Вы также можете заметить, что используется переменная suggestion. Но к тому времени, когда вы попытаетесь вызвать возвращенную функцию, outer уже вернется. Как suggestion доступен?

Ответ, конечно же, находится в замыкании — когда функция inner создается для возврата, используемая переменная suggestion «запирается» в замыкании, которое также создается. Эта переменная должна использоваться всякий раз, когда вызывается возвращаемая функция.

Почему я сделал акцент на самой переменной? Давайте посмотрим на следующий пример, который обычно задают как вопрос в интервью в подобных формах:

Вам дают это для цикла и спрашивают — что будет напечатано на экране.

Можно сказать: «Ну, setTimeout создает функцию для запуска каждый раз с текущим значением i, поэтому она печатает 0, 1, 2, 3 и 4».

… Что явно неправильно. Этот код на самом деле печатает 5, 5, 5, 5 и 5.
Позвольте мне указать причину:

«Ну, setTimeout создает функцию для запуска каждый раз с текущим значением i , поэтому она печатает 0, 1, 2, 3 и 4».

Поскольку мы узнали, что ссылка на саму переменную «заблокирована» внутри замыкания, а не значение — когда цикл завершает работу, значение i на самом деле равно 5, и поэтому каждый console.log будет просто печатать 5.

Как это исправить? Что ж, есть несколько способов сделать это, хотя я чувствую, что этот демонстрирует наибольшее понимание концепции. Посмотрите на него и посмотрите, понимаете ли вы, почему он решает проблему и как.

Как видите, я использую выражение немедленно вызываемой функции (IIFE), чтобы передать i замыканию, которое будет создано IIFE, а затем текущее значение i заблокировано для использовать внутри внутренней функции setTimeout (как x), в результате чего получаются 0, 1, 2, 3 и 4.

Ошибки, связанные с замыканием, случаются каждый день, когда функции вызываются в лексическом окружении, отличном от ожидаемого. Хорошо изучите эту тему.

Контекст выполнения

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

Вы должны использовать this, когда вам нужно сослаться на свойство текущего объекта. Например:

В функции getPrice мы хотим использовать свойство цены объекта, поэтому мы обращаемся к нему с помощью this. Как и ожидалось, запуск этого кода даст «Цена 17».

Однако к чему на самом деле относится this? Что произойдет после этого изменения:

Казалось бы, получив ссылку на getPrice от pancake, мы должны напечатать то же самое, верно? Итак… это напечатает «Цена не определена».
Знаете почему?

Ответ зависит от контекста выполнениядля getPrice.
В первом примере он вызывается "в" pancake, поэтому pancake становится контекстом выполнения для этой функции, а this ссылается на нее. .

Во втором примере функция удалена из ожидаемого контекста.
Итак, когда мы запустим его, каково значение this? Каков глобальный контекст выполнения, в котором работает функция (поскольку она находится в глобальной области видимости)?

Это зависит от того, где вы его запускаете. Если вы откроете инструменты разработчика в браузере и введете console.log(this), вы получите объект window, который является глобальным контекстом выполнения для JavaScript, работающего в браузере, и, естественно, не имеет свойства с именем price. Таким образом, мы получаем неопределенную цену.

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

Прототипы

Что общего у [1, 2, 3] и [4, 5]? Они оба массивы.
В других языках программирования можно сказать, что они оба одного класса, но в JavaScript у нас другая объектная система, и она основана на прототипах. Чтобы создать новый массив за кулисами, вы на самом деле не обращаетесь к какому-то «чертежу», который определяет структуру объекта. Вместо этого вы ссылаетесь на фактический экземпляр объекта. Объекты наследуют свои свойства непосредственно от других объектов.

Что это обозначает? , со «скрытым» свойством под названием __proto__.
Например, для массивов __proto__ указывает на Array.prototype — объект, содержащий все известные и любимые вами методы работы с массивами, такие как indexOf , map или concat.

Когда необходимо создать новый массив, вызывается функция constructor для создания этого экземпляра. Таким образом, Array.prototype.constructor на самом деле является самим Array. Затем сам прототип назначается свойству __proto__ экземпляра, и именно так методы вызываются для этого экземпляра.

Почему это интересно? Допустим, вам нужно добавить метод ко всем массивам. Во время выполнения (!). Это сбрасывает все элементы массива на 0.
Вы можете сделать следующее:

Вывод покажет, что arr теперь [0, 0, 0].

Как это произошло? Мы добавили новое свойство прототипа массива, на которое указывают все массивы, и определили его как функцию, которая делает то, что нам нужно. С этого момента все массивы теперь имеют доступ к этому методу.
Волшебство, да?

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

Давайте посмотрим на этот пример:

При запросе элементов DOM вы часто получаете NodeList или HTMLCollection. Они действительно выглядят как массивы, но теперь мы понимаем, что они из другого прототипа — а значит, доступ к Array методам прототипа не гарантируется. Но нам бы очень хотелось иногда использовать на них map или forEach. Как мы можем этого добиться?

Обратите внимание, как в первом примере мы ссылались на массив как this, чтобы сбросить его элементы до 0? Ну, мы узнали, что для того, чтобы установить this в то, что нам нужно, мы должны контролировать контекст выполнения и предоставлять правильное лексическое окружение. Мы можем сделать это, используя call или apply.

Давайте посмотрим, как это делается:

Продвинутая вещь, на которую следует обратить внимание — откуда берется call?

Ответ… от прототипа map, который является прототипом Function!

call получает значение this и аргументы, предоставленные индивидуально.
apply получает значение this и аргументы, переданные в виде массива.

Давайте повеселимся с этим и прототипами.
Вот сложный пример — добавление встроенного Math.max в прототип массива. Улов? Math.max получает числа в качестве параметров (в отличие от использования this).

Это была прекрасная возможность использовать apply — потому что мы хотим получить максимум от массива, а apply ожидает массив аргументов для второго параметра. null был предоставлен в качестве контекста, потому что Math.max не ссылается на this при его использовании.

Знание прототипов является ключевым, когда дело доходит до понимания JavaScript высокого класса, и часто проверяется на собеседованиях. Изучай хорошо!

Асинхронное программирование

JavaScript является однопоточным. Это означает, что у него есть один стек вызовов и одна куча памяти. Он должен выполнить кусок кода, прежде чем перейти к следующему.

Учитывая это, можете ли вы объяснить, как работают такие функции, как setTimeout или setInterval? Кроме того, объясните, что будет напечатано:

Ответ: 3, затем 1 и, наконец, 2. Знаете почему?

Несмотря на то, что операции setTimeout рассчитаны на выполнение в 0 мс, они не выполняются немедленно. Он помещается в очередь обратного вызова для запуска, когда у основного (и единственного) потока есть «время» для этого. До этого времени они будут сидеть в очереди и ждать.

А console.log(3)? Он просто помещается прямо в стек и готов к выполнению.

Вся эта операция управляется так называемым циклом событий.
Он загружает в стек обратные вызовы из очереди по порядку, когда стек пуст. Вот снимок после выполнения кода в примере:

Мы видим setTimeout операций, помещенных в очередь, и console.log(3), помещенных в стек. Затем задача в стеке выполняется, и только после этого — в стек подаются задачи из очереди, по Event Loop.

Этот механизм позволяет асинхронное программирование, когда у нас есть только один поток. Все происходит синхронно.

Я настоятельно рекомендую это видео, если вы хотите лучше понять Event Loop.

Обещания

Говоря об асинхронном программировании, мы должны упомянуть промисы.

Это объект, который возвращает значение в случае успеха или неудачи операции (например, сетевого запроса) и имеет три возможных состояния: ожидание, выполнение и отклонение.

Другими словами, асинхронный метод возвращает обещание предоставить значение когда-нибудь в будущем. В этом состоянии обещание находится в состоянии ожидание, ни выполнено, ни отклонено. Когда он, наконец, разрешится, он будет иметь одно из этих последних состояний.

Допустим, я хочу написать функцию, имитирующую сетевой запрос — для имитации или тестирования. Вы можете написать такую ​​функцию?

Функция mockRequest не заботится о своих аргументах, она просто создает новое обещание, которое всегда разрешается — в течение трех секунд, благодаря функции setTimeout. Затем я привязал then, чтобы напечатать разрешенное значение обещания.

Обещания являются основой разработки на стороне клиента с помощью JavaScript, и вам должно быть удобно создавать и использовать их через сторонних поставщиков.

Надеюсь, я помог вам узнать кое-что и улучшить свои знания и понимание JavaScript, чтобы вы могли писать более качественный код и более успешно проходить собеседования!

Для получения дополнительных статей, информации и интересных предложений посетите мою веб-страницу и станьте лучшим разработчиком.