Область видимости
Нам нужно разобраться в некоторых аспектах области видимости. Взгляните на следующий пример
1: let val1 = 22: function multiplyThis(n) {3: let ret = n * val14: return ret5: }6: let multiplied = multiplyThis(6)7: console.log('example of scope:', multiplied)
Основная идея здесь в том, что у нас есть переменные как в локальной области выполнения, так и в глобальной. Одно из сложностей JavaScript в том, как он ищет переменные. Если он не может найти переменную в локальной области переменной, то он будет пытаться найти в вызвавшей её области. И если не найдёт в этой вызвавшей области, то будет искать в вызвавшей уже эту область области. И так до тех пор, пока не дойдёт до глобальной области выполнения. (а если не найдёт и там, то значением будет ). Далее я подробнее объясню пример выше, чтобы прояснить это. Если вы понимаете, как работают области видимости, то можете пропустить.
- Объявление новой переменной в глобальной области выполнения и присваивание ей значения .
- Строки 2–5. Объявление новой переменной и присваивание ей описания функции.
- Строка 6. Объявление новой переменной в глобальной области выполнения.
- Извлечение переменной из памяти глобальной области выполнения и выполнение её как функции. Использование значения как аргумента.
- Новый вызов функции = новая область выполнения. Создание новой локальной области выполнения.
- В локальной области выполнения объявление переменной и присваивание значения .
- Строка 3. В локальной области выполнения объявление переменной .
- Строка 3 (продолжение). Выполнение всех умножений с двумя операндами, значениями переменных и . Поиск переменной в локальной области выполнения. Мы объявили её в шестом шаге. Её содержимое . Поиск переменной в локальной области выполнения. Локальная область выполнения не содержит переменной с названием . Давайте проверим вызвавшую область. Вызвавшая область — это глобальная область выполнения. Давайте поищем в глобальной области выполнения. Ну вот, она нашлась. Это было описано в первом шаге. Её значение .
- Строка 3 (продолжение). Умножить два операнда и присвоить результат переменной . 6 * 2 = 12. Теперь значение .
- Возвращение переменной . Локальна область выполнения разрушена вместе с переменными и . Переменная не разрушена, так как является частью глобальной области выполнения.
- Назад к строке 6. В области вызова число присвоено переменной .
- Наконец строка 7. Мы отображаем значение переменной в консоли.
Итак, в этом примере нам нужно запомнить то, что у функции есть доступ к переменным в вызвавшей её области. Формальное название такого феномена — область видимости.
Метод setTimeout
Метод запускается только один раз за вызов, а значит после завершения выполнения функции прекращает свою работу.
Основной и наиболее часто используемый синтаксис этой функции:
В этом примере все просто: функция обратного вызова сработает по завершении временной задержки. Давайте рассмотрим пример, как использовать эту функцию на практике:
function greet () { alert ('Welcome!'); } setTimeout (greet, 2000) ; // приветствие появится через 2000 миллисекунд (2 секунды)
позволяет нам назначить столько аргументов, сколько необходимо для функции обратного вызова. Предположим, что мы хотим поприветствовать Джона — пользователя, вошедшего в нашу систему. Для этого нам необходимо просто добавить аргументы в конец списка параметров :
Посмотрим, как это реализовать:
const loggedInUser = 'Джон' ; function greet (userName) { alert ('Добро пожаловать' + userName + '!'); } setTimeout (greet, 2000, loggedInUser);
В приведенном примере в появившемся через две секунды всплывающем окне будет написано «Добро пожаловать, Джон!». Здесь мы взяли имя пользователя из переменной и добавили его в функцию обратного вызова в качестве параметра.
Важно: не передавайте аргументы функции обратного вызова напрямую и не ставьте скобки после имени функции внутри метода , если аргументов нет!
Глядя на код предыдущего примера у вас может возникнуть соблазн передать аргументы в качестве параметров функции обратного вызова. Например:
или
Это логическая ошибка. В приведенной выше строке кода функция обратного вызова выполняется немедленно, а параметр задержки уже не воспринимается интерпретатором. Получается, что мы не передаем объект функции в качестве метода обратного вызова в , а скорее устанавливаем возвращаемое значение функции. В такой ситуации таймер будет проигнорирован.
Чтобы исправить эту ошибку, просто измените шаблон вызова, удалите скобки после имени функции, добавив аргументы (если они есть) в конец списка параметров .
Очередь событий
События возникают в ответ на пользовательский ввод (с мыши или клавиатуры) или в ответ на внутренние события, например окончание загрузки страницы. Однако, вызов событий асинхронен породившему его вводу.
Пользовательский ввод может случиться в тот момент, когда обработчик другого события всё ещё выполняется. В таком случае действия пользователя буферизуются, и когда модуль обработки событий вновь свободен, происходит обработка событий, соответствующих буферизованным действиям. События всегда возникают в правильном порядке, но между действием и вызовом события может произойти заметная задержка, если код некоторого обработчика события займёт продолжительное время.
Internet Explorer и Mozilla совершенно не реагируют на пользовательские действия в течение выполнения скриптов-обработчиков. Даже панели инструментов браузеров блокируются. Хотя пользователь по-прежнему может, например, нажимать на кнопки, и эти действия заносятся в буфер, никакой наблюдаемой реакции они не вызывают. Это может дезориентировать пользователя, который, не осознав, что его действие было учтено, вероятно, попробует нажать кнопку несколько раз, что может привести к нежелательным последствиям. Пользователь даже может решить, что браузер завис вследствие ошибки.
Opera же продолжает визуально реагировать на действия пользователя (например, нажатия кнопок) даже во время исполнения другого скрипта. Однако действия по-прежнему буферизуются и обрабатываются последовательно, как и в других браузерах. Таким образом, действия по умолчанию для события не производятся до тех пор, пока обработчик событий не доберётся до него. Это тоже может озадачить пользователя, хотя, наверное, не так сильно, как полная блокировка IE и Mozilla.
Отсюда следует сделать вывод, что скрипты-обработчики событий никогда не должны занимать много времени
С особой осторожностью следует отнестись к синхронным запросам , так как они могут вызвать заметную задержку, которая заблокирует браузер или окно документа
Метки для break/continue
Бывает, нужно выйти одновременно из нескольких уровней цикла сразу.
Например, в коде ниже мы проходимся циклами по и , запрашивая с помощью координаты с до :
Нам нужен способ остановить выполнение если пользователь отменит ввод.
Обычный после лишь прервёт внутренний цикл, но этого недостаточно. Достичь желаемого поведения можно с помощью меток.
Метка имеет вид идентификатора с двоеточием перед циклом:
Вызов в цикле ниже ищет ближайший внешний цикл с такой меткой и переходит в его конец.
В примере выше это означает, что вызовом будет разорван внешний цикл до метки с именем , и управление перейдёт со строки, помеченной , к .
Можно размещать метку на отдельной строке:
Директива также может быть использована с меткой. В этом случае управление перейдёт на следующую итерацию цикла с меткой.
Метки не позволяют «прыгнуть» куда угодно
Метки не дают возможности передавать управление в произвольное место кода.
Например, нет возможности сделать следующее:
Вызов возможен только внутри цикла, и метка должна находиться где-то выше этой директивы.
Решение — bind
Лучшее решение в этой ситуации — связать () методы, которые будут переданы из первоначального объекта или класса. Есть несколько способов связывания функций, но самый распространенный (даже в React) — это связать их в конструкторе. Нужно добавить эту строку в конструктор :
this.increaseCount = this.increaseCount.bind(this)
Можно привязать любую функцию к любому контексту. Необязательно привязывать функцию к контексту, в котором она объявлена (хотя это самый распространенный случай). Вместо этого, можно привязать ее к другому контексту. С помощью всегда устанавливается контекст для объявления функции. Это означает, что все вызовы для этой функции получат связанный контекст, как и в случае с . Существует еще два способа установки контекста.
Устаревший способ: использование while-loop
Одним из решений является использование цикла while, который выполняется до тех пор, пока не будет найден родительский узел.
function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }
Используя пример HTML, приведенный выше, это будет выглядеть следующим образом:
var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // выводит "123"
Это решение далеко не идеально. Представьте, что вы хотите использовать идентификаторы, классы или любой другой тип селектора вместо имени тега. По крайней мере, это позволяет использовать переменное количество дочерних узлов между родительским узлом и нашим источником.
Объект литералы
Оператор расширения очень схоже используется при создании объектов. Вы можете взять свойства одного объекта и включить их в новый объект, созданный объект литералом. Этот функционал доступен с ES2018 версии.
Имейте ввиду, что берет только собственные (не наследуемые) и перечислимые свойства объекта, все другие свойства будут попросту игнорироваться.
Поверхностная копия
Тут всё очень схоже с массивами. Вы можете смело склеивать и клонировать объекты.
Это может быть хорошей альтернативой для клонирования объектов с помощью
Но обратите внимание, что это поверхностная копия. Новый объект создастся, но клонированные свойства все равно будут от оригинала, а не клонами
Потерянный прототип
Клонируя объект способом выше вам стоит знать то, что прототип оригинального объекта не сохранится. Там просто копируются свойства исходного объекта и создастся совершенно новый объект, используя объект литерал, у которого прототип .
Конфликты свойств
Что случиться когда вы укажите свойство через оператор расширения, но это свойство уже будет существовать в объекте? Это не приведет к ошибке. Просто напросто, если будет несколько свойств объекта с одним и тем же именем, но разными свойствами, то победит последний.
Изменение иммутабельных объектов
Момент, когда ранее объявленное свойство с тем же именем перезаписывает другое, можно применить при изменении иммутабельных объектов. Когда вы работаете с таковыми или не хотите напрямую изменять изменять объекты, вы можете смело применить оператор расширения для создания нового объекта, как измененного варианта исходной копии.
Так как в оригинальной копии есть и в дальнейшем оно также используется, то будет взято значение при последнем её упоминании. Исходный объект не изменится в любом случае, но создастся новый объект.
Конвертируем итерируемые в массив при помощи Spread
Тут все это очень легко делается при помощи оператора расширения. Зачастую итерируемые объекты ограничены в выборе доступных для них методов. Но конвертируя их в массив, вы сразу же получаете доступ ко ВСЕМ прекрасным методам, доступным для массивов, таким как , , .
Уже готовые итерируемые
В JavaScript у нас есть несколько уже готовых итерируемых объектов, которые мы можем конвертировать в массив при помощи :
Строки
Массивы
Map
Set
Есть ещё один, но мы о нем пока не будем говорить это — .
Так в чем разница?
Разница в определении.
работает для:
Массивоподобных объектов (объекты с свойством длины и индексированными элементами)
Итерируемых объектов
работает только для:
Итерируемых объектов
Итак, давайте посмотрим на этот массивоподобный объект:
Тут мы получаем ошибку о том, что не является итерируемым.
Прогрессивный рендеринг
Ход рендеринга страницы на экране не всегда синхронизирован с построением DOM. Порядок рендеринга в ходе загрузки страницы является довольно непредсказуемым. В зависимости от скорости подключения и размера страницы, браузер может, прежде чем начать рендеринг, ждать загрузки всей страницы, либо, в случае медленного соединения, может рендерить страницу по частям.
Следует иметь в виду, что интерфейс реагирует на пользовательские события с того момента, как страница начинает рендериться. Это может привести к проблемам с «ссылками вперёд», если обработчик события ссылается на элемент, который встречается далее по ходу документа.
Пример опасного кода:
<button onclick="document.getElementById('lamp').backgroundColor = 'yellow'"> Жми сюда, чтобы включить лампу! </button> <div id='lamp'>O</div>
Проблема здесь в том, что элемент ‘lamp’ может быть ещё не распарсен в тот момент, когда кнопка нажата. Обработчик события никогда не должен ссылаться на элементы, определённые далее в документе.
В более сложных пользовательских интерфейсах отказ от ссылок вперёд между элементами интерфейса может быть неприемлем. Вместо этого следует сделать все элементы управления отключенными по умолчанию и активировать их только в обработчике события , где мы можем быть уверены, что загрузка всей страницы завершена.
Заметим, что также ждёт окончания загрузки всех изображений (и фреймов, и т.п.). Если на странице есть большие изображения, это может занять длительное время. Обходным решением является активация страницы посредством встроенного скрипта внизу страницы. Он будет выполнен при окончании загрузки страницы, но не будет дожидаться загрузки внешних ресурсов.
Основы this
тесно связано с тем, в каком контексте вы находитесь в программе. Начнем с самого начала. При наборе консоль выдает объект , самый внешний контекст JavaScript. При вводе следующего кода в Node.js:
console.log(this)
мы получаем пустойобъект . Это немного странно, но, скорее всего, это особенность Node.js. Однако при вводе следующего:
(function() {console.log(this);})();
получаем объект , относящийся к самому внешнему контексту. В этом контексте сохранены и. Не бойтесь немного поэкспериментировать с ними, чтобы узнать все их возможности. С этого момента между Node.js и браузером почти нет никаких различий. Мы будем использовать объект . Не забывайте, что в Node.js это будет объект , хотя это не играет особой роли.
Представьте, что вы пишите программу с функциями без вложений. Вы просто пишите одну строку за другой, не создавая никаких структур. Это значит, что нет необходимости следить за тем, где вы находитесь, поскольку вы остаетесь на одном уровне.
При наличии функций, программа обладает несколькими уровнями, а ключевое слово указывает на то, где вы находитесь, и какой именно объект является функцией.
Чего НЕ может JavaScript в браузере?
Возможности JavaScript в браузере ограничены ради безопасности пользователя. Цель заключается в предотвращении доступа недобросовестной веб-страницы к личной информации или нанесения ущерба данным пользователя.
Примеры таких ограничений включают в себя:
-
JavaScript на веб-странице не может читать/записывать произвольные файлы на жёстком диске, копировать их или запускать программы. Он не имеет прямого доступа к системным функциям ОС.
Современные браузеры позволяют ему работать с файлами, но с ограниченным доступом, и предоставляют его, только если пользователь выполняет определённые действия, такие как «перетаскивание» файла в окно браузера или его выбор с помощью тега .
Существуют способы взаимодействия с камерой/микрофоном и другими устройствами, но они требуют явного разрешения пользователя. Таким образом, страница с поддержкой JavaScript не может незаметно включить веб-камеру, наблюдать за происходящим и отправлять информацию в ФСБ.
-
Различные окна/вкладки не знают друг о друге. Иногда одно окно, используя JavaScript, открывает другое окно. Но даже в этом случае JavaScript с одной страницы не имеет доступа к другой, если они пришли с разных сайтов (с другого домена, протокола или порта).
Это называется «Политика одинакового источника» (Same Origin Policy). Чтобы обойти это ограничение, обе страницы должны согласиться с этим и содержать JavaScript-код, который специальным образом обменивается данными.
Это ограничение необходимо, опять же, для безопасности пользователя. Страница , которую открыл пользователь, не должна иметь доступ к другой вкладке браузера с URL и воровать информацию оттуда.
-
JavaScript может легко взаимодействовать с сервером, с которого пришла текущая страница. Но его способность получать данные с других сайтов/доменов ограничена. Хотя это возможно в принципе, для чего требуется явное согласие (выраженное в заголовках HTTP) с удалённой стороной. Опять же, это ограничение безопасности.
Подобные ограничения не действуют, если JavaScript используется вне браузера, например — на сервере. Современные браузеры предоставляют плагины/расширения, с помощью которых можно запрашивать дополнительные разрешения.
Мир до промисов — колбэки.
Должны ли мы использовать промисы для каждого асинхронного запроса? Нет. До промисов, мы используем колбэки. Колбэки это просто функция, которую вы вызываете, когда получаете отдаваемый результат. Давайте модифицируем предыдущий пример, чтобы разрешить колбэк.
Синтаксис ок, зачем нам тогда промисы?
Что если вы захотите сделать последующее асинхронное действие?
Давайте представим, что вместо простого сложения чисел единожды, нам надо будет сделать это 3 раза. В обычной функции, мы делаем это:
Как это выглядит с колбэками?
Этот синтаксис менее дружелюбен. Он выглядит как пирамида, но люди обычно называют подобное «колбэк адом», потому что колбэки, вложенные в колбэки, кхм, представьте, что у вас 10 колбэков и ваш код будет вложен 10 раз.
Асинхронные функции с async/await
Функция позволяет обрабатывать асинхронный код так, чтобы он выглядел синхронным. функции также используют промисы под капотом, но имеют более традиционный синтаксис JavaScript. Рассмотрим примеры этого синтаксиса.
Вы можете создать функцию, добавив ключевое слово перед функцией:
// Создать async функцию async function getUser() { return {} }
Хотя эта функция еще не обрабатывает ничего асинхронного, она ведет себя иначе, чем традиционная функция. Но! Если вы выполните функцию, вы обнаружите, что она возвращает обещание с и вместо возвращаемого значения.
Попробуйте вывод в консоль, вызвав функцию :
console.log(getUser())
Вы увидите следующее:
Output __proto__: Promise `PromiseStatus`: "fulfilled" `PromiseValue`: Object
Это означает, что вы можете обрабатывать асинхронную функцию с помощью так же, как и промис. Протестируйте это с помощью следующего кода:
getUser().then((response) => console.log(response))
Этот вызов передает возвращаемое значение анонимной функции, которая записывает значение в консоль.
При запуске этой программы вы получите следующее:
Output {}
Асинхронная функция может обрабатывать промис, вызываемый в ней, с помощью оператора ожидания . Этот оператор может использоваться в асинхронной функции и будет ждать, пока промис не разрешится, перед выполнением назначенного кода.
Теперь мы можем переписать запрос Fetch из последнего раздела с помощью async/await следующим образом:
// Обработка fetch при помощи async/await async function getUser() { const response = await fetch('https://api.github.com/users/octocat') const data = await response.json() console.log(data) } // Выполнение async функции getUser()
Здесь операторы гарантируют, что данные не будут выводиться в консоль, пока запрос не заполнит их данными.
Теперь окончательные данные можно обрабатывать внутри функции без необходимости использования :
Output login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...
Примечание. Во многих средах асинхронный режим необходим для использования , однако некоторые новые версии браузеров и Node позволяют использовать верхнего уровня, что позволяет обойтись без создания асинхронной функции для обертывания .
Наконец, поскольку мы обрабатываем выполненное обещание в асинхронной функции, вы также можете обрабатывать ошибку внутри функции. Вместо использования метода с , для обработки исключения вы будете использовать шаблон /.
// Обработка success и errors при помощи async/await async function getUser() { try { // Обработка success const response = await fetch('https://api.github.com/users/octocat') const data = await response.json() console.log(data) } catch (error) { // Обработка ошибки в catch console.error(error) } }
Программа теперь перейдет к блоку , если получит ошибку, и запишет эту ошибку в консоль.
Современный асинхронный код JavaScript чаще всего обрабатывается с синтаксисом /, но важно иметь практические знания о том, как работают промисы, тем более что промисы могут иметь дополнительные функции, которые не могут быть обработаны с помощью /, например, объединение обещаний с. Заключение
Заключение
Поскольку веб-API часто предоставляют данные асинхронно, изучение того, как обрабатывать результат асинхронных действий, является важной частью работы разработчика JavaScript. Мы рассмотрели, как среда хоста использует цикл событий для обработки порядка выполнения кода со стеком и очередью
Мы также опробовали примеры трех способов обработки успеха или отказа асинхронного события с помощью обратных вызовов, промисов и синтаксиса /. Наконец, мы использовали Fetch Web API для обработки асинхронных действий.
Читайте больше по теме:
setTimeout
Синтаксис:
Параметры:
- Функция или строка кода для выполнения.
Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется. - Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0.
- , …
- Аргументы, передаваемые в функцию (не поддерживается в IE9-)
Например, данный код вызывает спустя одну секунду:
С аргументами:
Если первый аргумент является строкой, то JavaScript создаст из неё функцию.
Это также будет работать:
Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так:
Передавайте функцию, но не запускайте её
Начинающие разработчики иногда ошибаются, добавляя скобки после функции:
Это не работает, потому что ожидает ссылку на функцию. Здесь запускает выполнение функции, и результат выполнения отправляется в . В нашем случае результатом выполнения является (так как функция ничего не возвращает), поэтому ничего не планируется.
Вызов возвращает «идентификатор таймера» , который можно использовать для отмены дальнейшего выполнения.
Синтаксис для отмены:
В коде ниже планируем вызов функции и затем отменяем его (просто передумали). В результате ничего не происходит:
Как мы видим из вывода , в браузере идентификатором таймера является число. В других средах это может быть что-то ещё. Например, Node.js возвращает объект таймера с дополнительными методами.
Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным.
Для браузеров таймеры описаны в стандарта HTML5.
Итого
Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.
- Если функция объявлена в основном потоке кода, то это Function Declaration.
- Если функция создана как часть выражения, то это Function Expression.
Между этими двумя основными способами создания функций есть следующие различия:
Function Declaration | Function Expression | |
---|---|---|
Время создания | До выполнения первой строчки кода. | Когда управление достигает строки с функцией. |
Можно вызвать до объявления | (т.к. создаётся заранее) | |
Условное объявление в |
Иногда в коде начинающих разработчиков можно увидеть много Function Expression. Почему-то, видимо, не очень понимая происходящее, функции решают создавать как , но в большинстве случаев обычное объявление функции – лучше.
Если нет явной причины использовать Function Expression – предпочитайте Function Declaration.
Сравните по читаемости:
Function Declaration короче и лучше читается. Дополнительный бонус – такие функции можно вызывать до того, как они объявлены.
Используйте Function Expression только там, где это действительно нужно и удобно.