что такое область видимости scope
Область видимости. Основы
Одной из самых основных идей практически всех языков программирования является возможность сохранять значения в переменных, а позже извлекать или менять эти значения.
Правила добавление переменных в нашу программу так, чтобы потом можно было их найти и получить их значение, должны определяться четкими границами. Эти границы определяют, какие переменные будут нам доступны в определенных местах программы, и процесс поиска переменных по их идентификатору (имени).
Если дать более техническое определение, то оно звучит так:
Область видимости переменных или просто “Область видимости” (англ. variable scope или просто scope) — это такая область программы, в пределах которой установлена связь между некоторой переменной и её идентификатором (именем), по которому можно получить значение этой переменной. За пределами области видимости тот же самый идентификатор может быть связан с другой переменной, либо быть свободным (вообще не связанным ни с какой из переменных).
Для понимания этой концепции можно привести следующую аналогию. Представим человека, которому была назначена деловая встреча в некотором бизнес-центре, где он раньше не был. Он заходит в нужный кабинет и так как там еще никого нет, он решает узнать время, для этого ему необходимы часы. Этот человек начинает искать часы, обнаруживает настольные часы и узнаёт по ним время.
Поэтому, в данном кабинете переменная, которая представляет собой “настольные часы”, в JavaScript можно объявить как
Разовьем аналогию дальше и представим, что хоть часов в кабинете и нет, этот человек знает, что в холле здания есть настенные часы. Он знает об этом, так как проходя в кабинет, отметил их наличие. И поэтому, он может получить нужное значение (время) из своего окружения — “выглянуть” из кабинета и посмотреть время на настенных часах.
В этой аналогии здание — это еще одна область видимости, которая также является окружением для области видимости “кабинет”. В данном здании идентификатор “часы” ( clock ) связан с переменной “настенные часы в холле”. И объявление этой переменной будет следующим:
Мы можем продолжить эту аналогию. Представим, что и в здании нет часов, но они есть на улице. Поэтому, даже находясь в кабинете, он может обратиться к своему окружению — выглянуть на улицу и посмотреть время на уличных часах.
В JavaScript области видимости ограничиваются функциями (функциональная область видимости) или блоками инструкций (блочная область видимости).
Поэтому, пример с часами на JavaScript можно записать так:
Также необходимо отметить, что переменные, объявленные внутри функции, недоступны снаружи, то есть даже если часы есть в кабинете, а человек находится в здании или на улице, где нет никаких часов, то обратится к часам в кабинете он не сможет. Более того, он в принципе не имеет никакого представления о содержимом кабинета, пока туда не зайдет.
Если нам необходимо, чтобы переменная была доступна отовсюду, то её необходимо объявить вне всех функций, то есть глобально.
В нашей аналогии, пусть это будет вселенная, где есть часы, которые можно увидеть из любой точки.
Переменные называются глобальными, если они доступны из любой точки программы. То есть те, которые объявлены в глобальной, самой внешней области видимости. Переменные, которые объявлены внутри функции и недоступны снаружи, называются локальными переменными. Область видимости, ограниченная функцией или блоком, это локальная область видимости.
Глобальная область видимости не вложена ни в какие другие области и находится вне всех функций, являясь родительской для всех других вложенных в неё областей видимости. И для объявления глобальной переменной clock наш код можно переписать так:
В случае, если одна и та же переменная объявлена в нескольких вложенных областях видимости, её значение будет браться из текущей области видимости, где она запрашивается. Например:
Это называется “затенение переменных”, когда текущая переменная скрывает значение переменной объявленной в родительской области видимости. Вообще, чтобы избежать путаницы при командной разработке и неясности в том, в каких переменных что хранится, лучше не применять затенение и использовать разные имена переменных.
Пока в примере мы разбирали локальные области, которые были ограничены функциями. Но также область видимости может быть ограничена блоком инструкций.
Области видимости и замыкания в JavaScript
Данная публикация представляет собой перевод материала «JavaScript Scope and Closures» под авторством Zell Liew, размещенного здесь.
Области видимости и замыкания важны в JavaScript, однако они сбивали меня с толку, когда я только начинал их изучать. Ниже приведены объяснения этих терминов, которые помогут вам разобраться в них.
Начнем с областей видимости
Область видимости
Область видимости в JavaScript определяет, какие переменные доступны вам. Существуют два типа областей видимости: глобальная и локальная.
Глобальная область видимости
Если переменная объявлена вне всех функций или фигурных скобок ( <> ), то считается, что она определена в глобальной области видимости.
Примечание: это верно только для JavaScript в веб браузерах. В Node.js глобальные переменные объявляются иначе, но мы не будем касаться Node.js в этой статье.
Как только происходит объявление глобальной переменной, можно использовать эту переменную везде в коде, даже в функциях.
Итак, следует всегда объявлять локальные переменные, а не глобальные.
Локальная область видимости
Переменные, которые используются только в определенной части кода, считаются помещенными в локальную область видимости. Такие переменные называются локальными.
В JavaScript выделяют два типа локальных областей видимости:
Сначала рассмотрим область видимости функции
Область видимости функции
Переменная, объявленная внутри функции, доступна только внутри функции. Код снаружи функции не имеет к ней доступа.
В примере ниже, переменная hello находится внутри области видимости функции sayHello :
Область видимости блока
В примере ниже, можно увидеть, что переменная hello находится внутри области видимости фигурных скобок:
Блочная область видимости является частным случаем области видимости функции, т.к. функции объявляются с фигурными скобками (кроме случаев использования стрелочных функций с неявным возвращением значения).
Подъем функции в области видимости
Функции, объявленные как «function declaration» (прим. перев.: функция вида function имя(параметры) ), всегда поднимаются наверх в текущей области видимости. Так, два примера ниже эквивалентны:
Если же функция объявляется как «function expression» (функциональное выражение) (прим. перев.: функция вида var f = function (параметры) ), то такая функция не поднимается в текущей области видимости.
Из-за этих двух возможных вариантов подъем функции потенциально может сбить с толку, поэтому не рекомендуется применять на практике. Всегда сначала объявляйте функции перед тем, как их использовать.
У функций нет доступа к областям видимости других функций
Функции не имеют доступа к областям видимости других функций, когда они объявляются раздельно, даже если одна функция используется в другой.
Вложенные области видимости
Когда функция объявляется в другой функции, то внутренняя функция имеет доступ к переменным внешней функции. Такой поведение называется разграничением лексических областей видимости.
В тоже время внешняя функция не имеет доступа к переменным внутренней функции.
Для визуализации того, как работают области видимости, можно представить одностороннее зеркало. Вы можете видеть тех, кто находится с другой стороны, но те, кто стоят с обратной стороны (зеркальной стороны), не могут видеть вас.
Если одни области видимости вложены в другие, то это можно представить как множество стеклянных поверхностей с принципом действия, описанным выше.
Если вы поняли все, что касается областей видимости, то можно сказать, что вы готовы к тому, чтобы разобраться с тем, что такое замыкания.
Замыкания
Всякий раз, когда вы вызываете функцию внутри другой функции, вы создаете замыкание. Говорят, что внутренняя функция является замыканием. Результатом замыкания обычно является то, что в дальнейшем становятся доступными переменные внешней функции.
Так как внутренняя функция является возвращаемым значением внешней функции, то можно немного сократить код, совместив возврат значения с объявлением функции.
Благодаря замыканиям появляется доступ к внешней функции, поэтому они обычно используются для двух целей:
Контроль побочных эффектов с помощью замыканий
Побочные эффекты появляются, когда производятся какие-то дополнительные действия помимо возврата значения после вызова функции. Множество вещей может быть побочным эффектом, например, Ajax-запрос, таймер или даже console.log:
Когда замыкания используются для контроля побочных эффектов, то, как правило, обращают внимание на такие побочные эффекты, которые могут запутать код (например, Ajax-запросы или таймеры).
Для пояснения рассмотрим пример
Допустим, требуется приготовить торт ко дню рождения вашего друга. Приготовление торта займет секунду, так как написанная функция выводит «торт испечён» через секунду.
Примечание: для краткости и простоты далее используются стрелочные функции из ES6.
Как можно заметить, такая функция имеет побочный эффект в виде таймера.
После вызова функции торт будет испечён ровно через секунду.
Проблема в том, что, допустим, не нужно, чтобы торт был испечён сразу после уточнения вкуса, а необходимо, чтобы торт был испечён позже, когда это потребуется.
С этого момента можно вызывать возвращенную функцию в любое время, когда это требуется, и торт будет приготовлен через секунду.
Так замыкания используются для уменьшения побочных эффектов — вызывается функция, которая активирует внутреннее замыкание по вашему желанию.
Приватные переменные с замыканиями
Как вы теперь знаете, переменные, созданные внутри функции, не могут быть доступны снаружи. Из-за того, что они не доступны, их также называют приватными переменными.
Однако иногда требуется доступ к такой приватной переменной, и для этого используются замыкания.
В примере выше saySecretCode — единственная функция (замыкание), которая выводит secretCode снаружи исходной функции secret. По этой причине такую функцию называют привилегированной.
Отладка областей видимости с помощью DevTools
Инструменты разработчика (DevTools) Chrome и Firefox упрощают отлаживание переменных в текущей области видимости. Существует два способа применения этого функционала.
Первый способ: добавлять ключевое слово debugger в код, чтобы останавливать выполнение JavaScript кода в браузерах с целью дальнейшей отладки.
Ниже пример с prepareCake :
Если открыть DevTools и перейти во вкладку Sources в Chrome (или вкладку Debugger в Firefox), то можно увидеть доступные переменные.
Можно также переместить debugger внутрь замыкания. Обратите внимание, как переменные области видимости изменяться в этот раз:
Второй способ: добавлять брейкпоинт напрямую в код во вкладке Sources (или Debugger) путем клика на номер строки.
Всё, что вы хотели знать об областях видимости в JavaScript (но боялись спросить)
У JS есть несколько концепций, связанных с областью видимости (scope), которые не всегда ясны начинающим разработчикам (и иногда даже опытным). Эта статья посвящена тем, кто стремится погрузиться в пучину областей видимости JS, услышав такие слова, как область видимости, замыкание, “this”, область имён, область видимости функции, глобальные переменные, лексическая область видимости, приватные и публичные области… Надеюсь, по прочтению материала вы сможете ответить на следующие вопросы:
— что такое область видимости?
— что есть глобальная/локальная ОВ?
— что есть пространство имён и чем оно отличается от ОВ?
— что обозначает ключевое слово this, и как оно относится с ОВ?
— что такое функциональная и лексическая ОВ?
— что такое замыкание?
— как мне всё это понять и сотворить?
Что такое область видимости?
В JS область видимости – это текущий контекст в коде. ОВ могут быть определены локально или глобально. Ключ к написанию пуленепробиваемого кода – понимание ОВ. Давайте разбираться, где переменные и функции доступны, как менять контекст в коде и писать более быстрый и поддерживаемый код (который и отлаживать быстрее). Разбираться с ОВ просто – задаём себе вопрос, в какой из ОВ мы сейчас находимся, в А или в Б?
Что есть глобальная/локальная ОВ?
Не написав ни строчки кода, мы уже находимся в глобальной ОВ. Если мы сразу определяем переменную, она находится в глобальной ОВ.
Глобальная ОВ – ваш лучший друг и худший кошмар. Обучаясь работе с разными ОВ, вы не встретите проблем с глобальной ОВ, разве что вы увидите пересечения имён. Часто можно услышать «глобальная ОВ – это плохо», но нечасто можно получить объяснение – почему. ГОВ – не плохо, вам нужно её использовать при создании модулей и API, которые будут доступны из разных ОВ, просто нужно использовать её на пользу и аккуратно.
Все мы использовали jQuery. Как только мы пишем
мы получаем доступ к jQuery в глобальной ОВ, и мы можем назвать этот доступ пространством имён. Иногда термин «пространство имён» используют вместо термина ОВ, однако обычно им обозначают ОВ самого уровня. В нашем случае jQuery находится в глобальной ОВ, и является нашим пространством имён. Пространство имён jQuery определено в глобальной ОВ, которая работает как ПИ для библиотеки jQuery, в то время как всё её содержимое наследуется от этого ПИ.
Что такое локальная ОВ?
Локальной ОВ называют любую ОВ, определённую после глобальной. Обычно у нас есть одна ГОВ, и каждая определяемая функция несёт в себе локальную ОВ. Каждая функция, определённая внутри другой функции, имеет своё локальное ОВ, связанное с ОВ внешней функции.
Если я определю функции и задам внутри переменные, они принадлежат локальной ОВ. Пример:
Все переменные из ЛОВ не видны в ГОВ. К ним нельзя получить доступ снаружи напрямую. Пример:
Переменная “name” относится к локальной ОВ, она не видна снаружи и поэтому не определена.
Функциональная ОВ.
Все локальные ОВ создаются только в функциональных ОВ, они не создаются циклами типа for или while или директивами типа if или switch. Новая функция – новая область видимости. Пример:
Так просто можно создать новую ОВ и локальные переменные, функции и объекты.
Лексическая ОВ
Если одна функция определена внутри другой, внутренняя имеет доступ к ОВ внешней. Это называется «лексической ОВ», или «замыканием», или ещё «статической ОВ».
С лексической ОВ довольно просто работать – всё, что определено в ОВ родителя, доступно в ОВ ребенка. К примеру:
В обратную сторону это не работает:
Всегда можно вернуть ссылку на “name”, но не саму переменную.
Последовательности ОВ
Последовательности ОВ определяют ОВ любой выбранной функции. У каждой определяемой функции есть своя ОВ, и каждая функция, определяемая внутри другой, имеет свой ОВ, связанный с ОВ внешней – это и есть последовательность, или цепочка. Позиция в коде определяет ОВ. Определяя значение переменной, JS идёт от самой глубоко вложенной ОВ наружу, пока не найдёт искомую функцию, объект или переменную.
Замыкания
Живут в тесном союзе с лексическими ОВ. Хорошим примером использования является возврат ссылки на функцию. Мы можем возвращать наружу разные ссылки, которые делают возможным доступ к тому, что было определено внутри.
Чтобы вывести на экран текст, недостаточно просто вызвать функцию sayHello:
Функция возвращает функцию, поэтому её надо сначала присвоить, а потом вызвать:
Можно конечно вызвать замыкание и напрямую:
Можно догадаться, что упрощённо их код выглядит примерно так:
Функция не обязана ничего возвращать, чтобы быть замыканием. Любой доступ к переменным извне текущей ОВ создаёт замыкание.
ОВ и ‘this’
Каждая ОВ назначает своё значение переменной “this”, в зависимости от способа вызова функции. Мы все использовали ключевое слово this, но не все понимают, как оно работает и какие есть отличия при вызовах. По умолчанию, оно относится к объекту самой внешней ОВ, текущему окну. Пример того, как разные вызовы меняют значения this:
Встречаются и проблемы со значением this. В следующем примере внутри одной и той же функции значение и ОВ могут меняться:
Здесь мы создали новую ОВ, которая вызывается не из обработчика событий, а значит, относится к объекту window. Можно, например, запоминать значение this в другой переменной, чтобы не возникало путаницы:
Иногда есть необходимость менять ОВ в зависимости от того, что вам нужно.
В примере:
Значение this не относится к перебираемым элементам, мы ничего не вызываем и не меняем ОВ. Давайте посмотрим, как мы можем менять ОВ (точнее, мы меняем контекст вызова функций).
.bind() не вызывает функцию, а просто привязывает значения переменных перед её вызовом. Как вы знаете, мы не можем передавать параметры в ссылки на функции:
Это можно исправить, создав новую вложенную функцию:
Приватные и публичные ОВ
В JavaScript, в отличии от многих других языков, нет понятий публичных и приватных ОВ, но мы можем их эмулировать при помощи замыканий. Для создания приватной ОВ мы можем обернуть наши функции в другие функции.
Но вызвать эту функцию напрямую нельзя:
Вот вам и приватная ОВ. Если вам нужна публичная ОВ, воспользуемся следующим трюком. Создаём пространство имён Module, которое содержит всё, относящееся к данному модулю:
Директива return возвращает методы, доступные публично, в глобальной ОВ. При этом они относятся к нужному пространству имён. Модуль Module может содержать столько методов, сколько нужно.
Не нужно стараться вываливать все методы в глобальную ОВ и загрязнять её. Вот так можно организовать приватную ОВ, не возвращая функции:
Мы можем вызвать publicMethod, но не можем privateMethod – он относится к приватной ОВ. В эти функции можно засунуть всё что угодно — addClass, removeClass, вызовы Ajax/XHR, Array, Object, и т.п.
Интересный поворот в том, что внутри одной ОВ все функции имеют доступ к любым другим, поэтому из публичных методов мы можем вызывать приватные, которые в глобальной ОВ недоступны:
Это повышает интерактивность и безопасность кода. Ради безопасности не стоит вываливать все функции в глобальную ОВ, чтобы функции, которые вызывать не нужно, не вызвали бы ненароком.
Пример возврата объекта с использованием приватных и публичных методов:
Удобно начинать название приватных методов с подчёркивания, чтобы визуально отличать их от публичных:
Удобно также возвращать методы списком, возвращая ссылки на функции:
Всё, что вы хотели знать об областях видимости в JavaScript (но боялись спросить)
У JS есть несколько концепций, связанных с областью видимости (scope), которые не всегда ясны начинающим разработчикам (и иногда даже опытным). Эта статья посвящена тем, кто стремится погрузиться в пучину областей видимости JS, услышав такие слова, как область видимости, замыкание, “this”, область имён, область видимости функции, глобальные переменные, лексическая область видимости, приватные и публичные области… Надеюсь, по прочтению материала вы сможете ответить на следующие вопросы:
— что такое область видимости?
— что есть глобальная/локальная ОВ?
— что есть пространство имён и чем оно отличается от ОВ?
— что обозначает ключевое слово this, и как оно относится с ОВ?
— что такое функциональная и лексическая ОВ?
— что такое замыкание?
— как мне всё это понять и сотворить?
Что такое область видимости?
В JS область видимости – это текущий контекст в коде. ОВ могут быть определены локально или глобально. Ключ к написанию пуленепробиваемого кода – понимание ОВ. Давайте разбираться, где переменные и функции доступны, как менять контекст в коде и писать более быстрый и поддерживаемый код (который и отлаживать быстрее). Разбираться с ОВ просто – задаём себе вопрос, в какой из ОВ мы сейчас находимся, в А или в Б?
Что есть глобальная/локальная ОВ?
Не написав ни строчки кода, мы уже находимся в глобальной ОВ. Если мы сразу определяем переменную, она находится в глобальной ОВ.
Глобальная ОВ – ваш лучший друг и худший кошмар. Обучаясь работе с разными ОВ, вы не встретите проблем с глобальной ОВ, разве что вы увидите пересечения имён. Часто можно услышать «глобальная ОВ – это плохо», но нечасто можно получить объяснение – почему. ГОВ – не плохо, вам нужно её использовать при создании модулей и API, которые будут доступны из разных ОВ, просто нужно использовать её на пользу и аккуратно.
Все мы использовали jQuery. Как только мы пишем
мы получаем доступ к jQuery в глобальной ОВ, и мы можем назвать этот доступ пространством имён. Иногда термин «пространство имён» используют вместо термина ОВ, однако обычно им обозначают ОВ самого уровня. В нашем случае jQuery находится в глобальной ОВ, и является нашим пространством имён. Пространство имён jQuery определено в глобальной ОВ, которая работает как ПИ для библиотеки jQuery, в то время как всё её содержимое наследуется от этого ПИ.
Что такое локальная ОВ?
Локальной ОВ называют любую ОВ, определённую после глобальной. Обычно у нас есть одна ГОВ, и каждая определяемая функция несёт в себе локальную ОВ. Каждая функция, определённая внутри другой функции, имеет своё локальное ОВ, связанное с ОВ внешней функции.
Если я определю функции и задам внутри переменные, они принадлежат локальной ОВ. Пример:
Все переменные из ЛОВ не видны в ГОВ. К ним нельзя получить доступ снаружи напрямую. Пример:
Переменная “name” относится к локальной ОВ, она не видна снаружи и поэтому не определена.
Функциональная ОВ.
Все локальные ОВ создаются только в функциональных ОВ, они не создаются циклами типа for или while или директивами типа if или switch. Новая функция – новая область видимости. Пример:
Так просто можно создать новую ОВ и локальные переменные, функции и объекты.
Лексическая ОВ
Если одна функция определена внутри другой, внутренняя имеет доступ к ОВ внешней. Это называется «лексической ОВ», или «замыканием», или ещё «статической ОВ».
С лексической ОВ довольно просто работать – всё, что определено в ОВ родителя, доступно в ОВ ребенка. К примеру:
В обратную сторону это не работает:
Всегда можно вернуть ссылку на “name”, но не саму переменную.
Последовательности ОВ
Последовательности ОВ определяют ОВ любой выбранной функции. У каждой определяемой функции есть своя ОВ, и каждая функция, определяемая внутри другой, имеет свой ОВ, связанный с ОВ внешней – это и есть последовательность, или цепочка. Позиция в коде определяет ОВ. Определяя значение переменной, JS идёт от самой глубоко вложенной ОВ наружу, пока не найдёт искомую функцию, объект или переменную.
Замыкания
Живут в тесном союзе с лексическими ОВ. Хорошим примером использования является возврат ссылки на функцию. Мы можем возвращать наружу разные ссылки, которые делают возможным доступ к тому, что было определено внутри.
Чтобы вывести на экран текст, недостаточно просто вызвать функцию sayHello:
Функция возвращает функцию, поэтому её надо сначала присвоить, а потом вызвать:
Можно конечно вызвать замыкание и напрямую:
Можно догадаться, что упрощённо их код выглядит примерно так:
Функция не обязана ничего возвращать, чтобы быть замыканием. Любой доступ к переменным извне текущей ОВ создаёт замыкание.
ОВ и ‘this’
Каждая ОВ назначает своё значение переменной “this”, в зависимости от способа вызова функции. Мы все использовали ключевое слово this, но не все понимают, как оно работает и какие есть отличия при вызовах. По умолчанию, оно относится к объекту самой внешней ОВ, текущему окну. Пример того, как разные вызовы меняют значения this:
Встречаются и проблемы со значением this. В следующем примере внутри одной и той же функции значение и ОВ могут меняться:
Здесь мы создали новую ОВ, которая вызывается не из обработчика событий, а значит, относится к объекту window. Можно, например, запоминать значение this в другой переменной, чтобы не возникало путаницы:
Иногда есть необходимость менять ОВ в зависимости от того, что вам нужно.
В примере:
Значение this не относится к перебираемым элементам, мы ничего не вызываем и не меняем ОВ. Давайте посмотрим, как мы можем менять ОВ (точнее, мы меняем контекст вызова функций).
.bind() не вызывает функцию, а просто привязывает значения переменных перед её вызовом. Как вы знаете, мы не можем передавать параметры в ссылки на функции:
Это можно исправить, создав новую вложенную функцию:
Приватные и публичные ОВ
В JavaScript, в отличии от многих других языков, нет понятий публичных и приватных ОВ, но мы можем их эмулировать при помощи замыканий. Для создания приватной ОВ мы можем обернуть наши функции в другие функции.
Но вызвать эту функцию напрямую нельзя:
Вот вам и приватная ОВ. Если вам нужна публичная ОВ, воспользуемся следующим трюком. Создаём пространство имён Module, которое содержит всё, относящееся к данному модулю:
Директива return возвращает методы, доступные публично, в глобальной ОВ. При этом они относятся к нужному пространству имён. Модуль Module может содержать столько методов, сколько нужно.
Не нужно стараться вываливать все методы в глобальную ОВ и загрязнять её. Вот так можно организовать приватную ОВ, не возвращая функции:
Мы можем вызвать publicMethod, но не можем privateMethod – он относится к приватной ОВ. В эти функции можно засунуть всё что угодно — addClass, removeClass, вызовы Ajax/XHR, Array, Object, и т.п.
Интересный поворот в том, что внутри одной ОВ все функции имеют доступ к любым другим, поэтому из публичных методов мы можем вызывать приватные, которые в глобальной ОВ недоступны:
Это повышает интерактивность и безопасность кода. Ради безопасности не стоит вываливать все функции в глобальную ОВ, чтобы функции, которые вызывать не нужно, не вызвали бы ненароком.
Пример возврата объекта с использованием приватных и публичных методов:
Удобно начинать название приватных методов с подчёркивания, чтобы визуально отличать их от публичных:
Удобно также возвращать методы списком, возвращая ссылки на функции: