scope javascript что это
Всё, что вы хотели знать об областях видимости в 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: область видимости простыми словами
Доброго времени суток, друзья!
Область видимости — важная концепция, определяющая доступность переменных. Данная концепция лежит в основе замыканий, разделяя переменные на глобальные и локальные.
В этой статье я постараюсь простыми словами объяснить, что такое область видимости в JavaScript.
1. Область видимости
Перед тем, как погружаться в детали, связанные с областью видимости, рассмотрим небольшой пример.
Допустим, мы определили переменную:
Мы легко можем вывести ее значение в консоль. Это понятно.
Теперь поместим объявление переменной message в блок if:
На этот раз при попытке доступа к переменной выбрасывается исключение ReferenceError: message is not defined.
Почему это произошло?
Потому что блок if создал область видимости для переменной message. И message доступна только внутри этой области.
Таким образом, доступность переменных ограничена областью видимости, в которой они определены.
Итак, область видимости — это зона доступности переменных.
2. Блочная область видимости
Блок кода в JavaScript определяет область видимости переменных, объявленных с помощью ключевых слов const и let:
Первый console.log() благополучно выводит значение переменной message в консоль, поскольку доступ к этой переменной осуществляется в той области видимости, в которой она определена.
Однако вызов второго console.log() приводит к возникновению ошибки, поскольку переменная message недоступна во внешней по отношению к ней области видимости: в текущем контексте message не существует.
В инструкциях if, for, while также создается блочная область видимости.
Переменные color и message существуют только внутри блока for.
Тоже самое справедливо для инструкции while:
message, определенная в while, доступна только внутри данного цикла.
В JavaScript вы можете создавать самостоятельные блоки кода. Они также определяют собственную область видимости:
2.1. var не имеет блочной области видимости
Как мы видели в предыдущих примерах, блок кода создает область видимости для переменных, объявленных с помощью ключевых слов const и let. Однако это не работает для переменных, объявленных с помощью ключевого слова var.
Переменная count, как и ожидалось, доступна внутри блока if. Однако, она доступна и за пределами данного блока!
Дело в том, что блок кода не создает области видимости для переменных, объявленных с помощью ключевого слова var. Но это делает функция.
3. Область видимости функции
Функции в JavaScript создают область видимости для всех переменных, независимо от того, с помощью какого ключевого слова они объявлены (var, const или let).
Функция run() создает область видимости. Переменная message доступна внутри функции, но недоступна снаружи.
Аналогичным образом функция создает область видимости для переменных, объявленных с помощью const и let, и даже для других функций и функциональных выражений:
4. Область видимости модуля
Модули ES6 также создают область видимости для переменных, функций и классов.
Модуль circle создает константу pi (для внутреннего использования):
Переменная pi объявляется внутри модуля circle и не экспортируется из него.
Затем модуль circle импортируется:
Переменная pi недоступна за пределами модуля circle (до тех пор, пока она не будет экспортирована с помощью export).
Модульная область видимости инкапсулирует модули. Это означает, что частные переменные (которые не экспортируются) используются для собственных нужд модуля и защищены от доступа извне.
Таким образом, можно сказать, что область видимости — это механизм инкапсуляции для блоков кода, функций и модулей.
5. Области видимости могут быть вложенными
Интересной особенностью областей видимости является то, что они могут быть вложены одна в другую.
В следующем примере функция run() создает область видимости, а внутри нее блок if создает еще одну область:
Область видимости блока if вложена в область видимости функции run().
Область видимости, находящаяся внутри другой области, называется внутренней областью видимости. В приведенном примере — это область видимости блока if.
Область видимости, содержащая другую область, называется внешней областью видимости. В приведенном примере — это область видимости фукнции run().
Что насчет доступности переменных? Нужно запомнить простое правило:
Переменные из внешней области видимости доступны во внутренней области.
Поэтому переменная message доступна внутри блока if.
6. Глобальная область видимости
Глобальная область видимости является самой внешней областью. Она доступна для любой внутренней или локальной области видимости. В браузере глобальной является область видимости, создаваемая при загрузке JavaScript-файла, указанного в атрибуте src тега script:
Замыкания, функции изнутри
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/closure.
В этой главе мы продолжим рассматривать, как работают переменные, и, как следствие, познакомимся с замыканиями. От глобального объекта мы переходим к работе внутри функций.
Лексическое окружение
Мы будем называть этот объект «лексическое окружение» или просто «объект переменных».
Пример
Посмотрим пример, чтобы лучше понимать, как это работает:
При вызове функции:
До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создаёт пустой объект LexicalEnvironment и заполняет его.
В данном случае туда попадает аргумент name и единственная переменная phrase :
В конце выполнения функции объект с переменными обычно выбрасывается и память очищается. В примерах выше так и происходит. Через некоторое время мы рассмотрим более сложные ситуации, при которых объект с переменными сохраняется и после завершения функции.
Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13.
Доступ ко внешним переменным
Из функции мы можем обратиться не только к локальной переменной, но и к внешней:
Это свойство никогда не меняется. Оно всюду следует за функцией, привязывая её, таким образом, к месту своего рождения.
Если переменная не найдена в функции – она будет искаться снаружи.
Именно благодаря этой механике в примере выше alert(userName) выводит внешнюю переменную. На уровне кода это выглядит как поиск во внешней области видимости, вне функции.
Всегда текущее значение
Значение переменной из внешней области берётся всегда текущее. Оно может быть уже не то, что было на момент создания функции.
Например, в коде ниже функция sayHi берёт phrase из внешней области:
Это естественно, ведь для доступа к внешней переменной функция по ссылке [[Scope]] обращается во внешний объект переменных и берёт то значение, которое там есть на момент обращения.
Вложенные функции
Внутри функции можно объявлять не только локальные переменные, но и другие функции.
К примеру, вложенная функция может помочь лучше организовать код:
Вложенные функции получают [[Scope]] так же, как и глобальные. В нашем случае:
Заметим, что если переменная не найдена во внешнем объекте переменных, то она ищется в ещё более внешнем (через [[Scope]] внешней функции), то есть, такой пример тоже будет работать:
Возврат функции
Рассмотрим более «продвинутый» вариант, при котором внутри одной функции создаётся другая и возвращается в качестве результата.
В разработке интерфейсов это совершенно стандартный приём, функция затем может назначаться как обработчик действий посетителя.
Здесь мы будем создавать функцию-счётчик, которая считает свои вызовы и возвращает их текущее число.
В примере ниже makeCounter создаёт такую функцию:
Если подробнее описать происходящее:
На этом создание «счётчика» завершено.
Возвращённая из makeCounter() функция counter помнит (через [[Scope]] ) о том, в каком окружении была создана.
Это и используется для хранения текущего значения счётчика.
Далее, когда-нибудь, функция counter будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт.
Переменную во внешней области видимости можно не только читать, но и изменять.
В примере выше было создано несколько счётчиков. Все они взаимно независимы:
Свойства функции
Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней, вот так:
Свойства функции не стоит путать с переменными и параметрами. Они совершенно никак не связаны. Переменные доступны только внутри функции, они создаются в процессе её выполнения. Это – использование функции «как функции».
А свойство у функции – доступно отовсюду и всегда. Это – использование функции «как объекта».
Если хочется привязать значение к функции, то можно им воспользоваться вместо внешних переменных.
В качестве демонстрации, перепишем пример со счётчиком:
При запуске пример работает также.
Принципиальная разница – во внутренней механике и в том, что свойство функции, в отличие от переменной из замыкания – общедоступно, к нему имеет доступ любой, у кого есть объект функции.
Например, можно взять и поменять счётчик из внешнего кода:
Иногда свойства, привязанные к функции, называют «статическими переменными».
В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог – такое вот свойство функции.
Итого: замыкания
Замыкание – это функция вместе со всеми внешними переменными, которые ей доступны.
Таково стандартное определение, которое есть в Wikipedia и большинстве серьёзных источников по программированию. То есть, замыкание – это функция + внешние переменные.
Тем не менее, в JavaScript есть небольшая терминологическая особенность.
Обычно, говоря «замыкание функции», подразумевают не саму эту функцию, а именно внешние переменные.
Иногда говорят «переменная берётся из замыкания». Это означает – из внешнего объекта переменных.
Иногда говорят «Вася молодец, понимает замыкания!». Что это такое – «понимать замыкания», какой смысл обычно вкладывают в эти слова?
«Понимать замыкания» в JavaScript означает понимать следующие вещи:
В следующих главах мы углубим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью.
Замыкания
Замыкание — это комбинация функции и лексического окружения, в котором эта функция была определена. Другими словами, замыкание даёт вам доступ к Scope (en-US) внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз при создании функции, во время её создания.
Лексическая область видимости
Рассмотрим следующий пример:
Выполните этот код и обратите внимание, что команда alert() внутри displayName() благополучно выводит на экран содержимое переменной name объявленной в родительской функции. Это пример так называемой лексической области видимости (lexical scoping): в JavaScript область действия переменной определяется по её расположению в коде (это очевидно лексически), и вложенные функции имеют доступ к переменным, объявленным вовне. Этот механизм и называется Lexical scoping (область действия, ограниченная лексически).
Замыкание
Рассмотрим следующий пример:
Если выполнить этот код, то результат будет такой же, как и выполнение init() из предыдущего примера: строка «Mozilla» будет показана в JavaScript alert диалоге. Что отличает этот код и представляет для нас интерес, так это то, что внутренняя функция displayName() была возвращена из внешней до того, как была выполнена.
На первый взгляд, кажется неочевидным, что этот код правильный, но он работает. В некоторых языках программирования локальные переменные-функции существуют только во время выполнения этой функции. После завершения выполнения makeFunc() можно ожидать, что переменная name больше не будет доступна. Однако, поскольку код продолжает нормально работать, очевидно, что это не так в случае JavaScript.
А вот немного более интересный пример — функция makeAdder :
По существу makeAdder — это фабрика функций: она создаёт функции, которые могут прибавлять определённое значение к своему аргументу. В примере выше мы используем нашу фабричную функцию для создания двух новых функций — одна прибавляет 5 к своему аргументу, вторая прибавляет 10.
add5 и add10 — это примеры замыканий. Эти функции делят одно определение тела функции, но при этом они сохраняют различные окружения. В окружении функции add5 x — это 5, в то время как в окружении add10 x — это 10.
Замыкания на практике
Замыкания полезны тем, что позволяют связать данные (лексическое окружение) с функцией, которая работает с этими данными. Очевидна параллель с объектно-ориентированным программированием, где объекты позволяют нам связать некоторые данные (свойства объекта) с одним или несколькими методами.
Следовательно, замыкания можно использовать везде, где вы обычно использовали объект с одним единственным методом.
Давайте рассмотрим практический пример: допустим, мы хотим добавить на страницу несколько кнопок, которые будут менять размер текста. Как вариант, мы можем указать свойство font-size на элементе body в пикселах, а затем устанавливать размер прочих элементов страницы (таких, как заголовки) с использованием относительных единиц em:
Тогда наши кнопки будут менять свойство font-size элемента body, а остальные элементы страницы просто получат это новое значение и отмасштабируют размер текста благодаря использованию относительных единиц.
Используем следующий JavaScript:
Эмуляция частных (private) методов с помощью замыканий
JavaScript не имеет встроенной возможности сделать такое, но это можно эмулировать с помощью замыкания. Частные методы полезны не только тем, что ограничивают доступ к коду, это также мощное средство глобальной организации пространства имён, позволяющее не засорять публичный интерфейс вашего кода внутренними методами классов.
Код ниже иллюстрирует, как можно использовать замыкания для определения публичных функций, которые имеют доступ к закрытым от пользователя (private) функциям и переменным. Такая манера программирования называется модульное программирование:
Заметьте, что счётчики работают независимо друг от друга. Это происходит потому, что у каждого из них в момент создания функцией makeCounter() также создавался свой отдельный контекст исполнения (окружение). То есть приватная переменная privateCounter в каждом из счётчиков это действительно отдельная, самостоятельная переменная.
Используя замыкания подобным образом, вы получаете ряд преимуществ, обычно ассоциируемых с объектно-ориентированным программированием, таких как изоляция и инкапсуляция.
Создание замыканий в цикле: Очень частая ошибка
Массив helpText описывает три подсказки для трёх полей ввода. Цикл пробегает эти описания по очереди и для каждого из полей ввода определяет, что при возникновении события onfocus для этого элемента должна вызываться функция, показывающая соответствующую подсказку.
Если вы запустите этот код, то увидите, что он работает не так, как мы ожидаем интуитивно. Какое поле вы бы ни выбрали, в качестве подсказки всегда будет высвечиваться сообщение о возрасте.
В качестве решения в этом случае можно предложить использование функции, фабричной функции (function factory), как уже было описано выше в примерах:
Соображения по производительности
Не нужно без необходимости создавать функции внутри функций в тех случаях, когда замыкания не нужны. Использование этой техники увеличивает требования к производительности как в части скорости, так и в части потребления памяти.
Как пример, при написании нового класса есть смысл помещать все методы в прототип его объекта, а не описывать их в тексте конструктора. Если сделать по-другому, то при каждом создании объекта для него будет создан свой экземпляр каждого из методов, вместо того, чтобы наследовать их из прототипа.
Давайте рассмотрим не очень практичный, но показательный пример:
Поскольку вышеприведённый код никак не использует преимущества замыканий, его можно переписать следующим образом:
Методы вынесены в прототип. Тем не менее, переопределять прототип — само по себе является плохой привычкой, поэтому давайте перепишем всё так, чтобы новые методы просто добавились к уже существующему прототипу.
Код выше можно сделать аккуратнее:
В обоих примерах выше методы определяются один раз — в прототипе. И все объекты, использующие данный прототип, будут использовать это определение без дополнительного расхода вычислительных ресурсов. Смотрите подробное описание в статье Подробнее об объектной модели.