Что такое скоуп в программировании
Объяснение Scope (Chain) в JavaScript с помощью визуализации
Это перевод статьи Лидии Холли, где она разбирает такое понятие как Scope (Chain) с помощью визуализации.
Давайте рассмотрим следующий код:
Для функции getPersonInfo цепочка областей видимости (scope chain) выглядит так:
Scope chain в основном представляет собой «цепочку ссылок» на объекты, которые содержат ссылки на значения и другие скоупы, на которые можно ссылаться в этом контексте выполнения. Scope chain создается при создании контекста выполнения, то есть создается в runtime.
Однако давайте сосредоточимся на scope. В следующих примерах пары key/value в контекстах выполнения представляют ссылки, которые в scope как переменные.
Мы можем спуститься по scope chain, но не можем подняться вверх по цепочке. (Хорошо, это может сбивать с толку, потому что некоторые люди говорят «вверх», а не «вниз», поэтому я просто перефразирую: вы можете перейти к внешним scope, но не к «более внутренним скоупам». Это можно визуализировать как водопад:
Возьмем этот код в качестве примера.
Эта попытка выдаст ошибку ReferenceError! Не удалось найти ссылку на переменную с именем city в глобальной области видимости, и не было других скоупов для поиска, и она не может подняться по цепочке областей видимости.
Таким образом, можно использовать область видимости как способ «защитить» свои переменные и повторно использовать имена переменных.
Помимо глобальной и локальной областей, существует также block scope. Переменные, объявленные с помощью ключевого слова let или const, ограничены ближайшими фигурными скобками ( <> ).
Это можно визуализировать следующим образом:
Как видите, есть глобальная область видимости (синим), область видимости функции (оранжевым) и две области видимости блока (желтым). Мы смогли объявить переменную дважды, так как переменные созданы внутри разных скоупов (в фигурных скобках).
Область (C++)
При объявлении программного элемента, такого как класс, функция или переменная, его имя может быть «видимым» и использоваться в определенных частях программы. Контекст, в котором отображается имя, называется его областью действия. Например, если объявить переменную x внутри функции, x она будет видна только в теле этой функции. Он имеет локальную область. У вас могут быть другие переменные с одним и тем же именем в программе; пока они находятся в разных областях, они не нарушают правило одного определения и не вызывают ошибку.
Для автоматических нестатических переменных область также определяет, когда они создаются и уничтожаются в памяти программ.
Существует шесть видов областей:
Область пространства имен Имя, объявленное в пространстве именвне любого класса или определения перечисления или блока функции, видимо от его точки объявления до конца пространства имен. Пространство имен может быть определено в нескольких блоках для разных файлов.
Локальная область Имя, объявленное внутри функции или лямбда-выражения, включая имена параметров, имеет локальную область. Они часто называются «локальными». Они видны только от их точки объявления до конца функции или тела лямбда-выражения. Локальная область — это разновидность области блока, которая обсуждается далее в этой статье.
Область класса Имена членов класса имеют область класса, которая расширяется по всему определению класса независимо от точки объявления. Доступность членов класса дополнительно контролируется с помощью public private protected ключевых слов, и. Доступ к открытым или защищенным членам возможен только с помощью операторов выбора членов (. или —) или операторов указателя на член (. или — ).
Область инструкции Имена, объявленные в if while операторе,, или, switch видимы до конца блока инструкции.
Скрытие имен
Можно скрыть имя, объявив его в закрытом блоке. На следующем рисунке i повторно объявляется во внутреннем блоке, таким образом скрывая переменную, связанную с i во внешней области видимости блока.
Блокировка области и скрытия имен
Выходные данные программы, представленной на рисунке, выглядят следующим образом.
Скрытие имен классов
В следующем примере показано, как объявить указатель на объект типа Account с помощью class ключевого слова:
Повторное использование имен идентификаторов считается плохим стилем программирования, как показано в следующем примере.
Скрытие имен с глобальной областью видимости
Можно скрыть имена с глобальной областью, явно объявляя одно и то же имя в области видимости блока. Однако доступ к именам глобальных областей можно получить с помощью оператора разрешения области ( :: ).
Введение в JavaScript scope
Введение в JavaScript scope (область видимости функции, область видимости блока).
Область видимости или Scope
Область видимости (scope) определяет видимость или доступность переменной (другого ресурса) в области твоего кода.
Глобальная область видимости или Global Scope
В JavaScript есть только одна глобальная область. Область за пределами всех функций считается глобальной областью, и переменные, определенные в глобальной области, могут быть доступны и изменены в любых других областях.
Локальная область видимости или Local Scope
Переменные, объявленные внутри функций, становятся локальными для функции и рассматриваются в соответствующей локальной области. Каждая функция имеет свою область видимости. Одна и та же переменная может использоваться в разных функциях, поскольку они связаны с соответствующими функциями и не являются взаимно видимыми.
Область видимости функции
Область видимости блока
Лексическая область видимости
Динамическая область видимости
Динамическая область видимости, по понятным причинам, подразумевает, что существует модель, в которой область видимости может определяться динамически во время выполнения, а не статически во время создания. Например:
Динамическая область видимости, напротив, не связана с тем, как и где объявляются функции и области, а связана с тем, откуда они вызываются. Другими словами, цепочка областей видимости основана на стеке вызовов, а не на вложении областей видимости в коде.
Как такое может быть?
Можно подумать что это странно.
Но JavaScript, на самом деле, не имеет динамической области видимости. Он имеет только лексическую область. А вот механизм this подобен динамической области видимости.
И наконец: this заботится о том, как была вызвана функция. Это показывает нам, насколько тесно механизм this связан с идеей динамической области видимости.
Окружение — Введение в программирование
Важное примечание
Изначально в начале конспекта и урока речь шла о глобальном и локальном окружении, но объяснялась фактически глобальная и локальная область видимости. В видео эта тема до сих пор называется «глобальное и локальное окружение», текстовая версия урока исправлена.
Прежде, чем приступить к уроку, разберёмся с терминологией:
Путаница возникает ещё потому, что в разделе про Область видимости мы смотрим на примеры, которые работают по правилу «лексическая область видимости». От этого никуда не деться, так как язык JavaScript работает именно так.
Транскрипт урока
Часть I. Окружение
Давайте поговорим об окружении. Наша планета огромна, но мы все делим её. Если вы построите химический завод, неплохо бы изолировать его от окружающего мира, чтобы то, что в нём происходит оставалось внутри. Вы можете сказать, что в этом здании своё окружение, микроклимат, изолированный от внешней окружающей среды. В программировании такая изоляция называется областью видимости.
Ваша программа имеет подобную структуру по похожим причинам. То, что вы создаёте снаружи — снаружи функций, инструкций if, циклов и других блоков кода — находится во внешней, глобальной области видимости.
У нас нет доступа к x снаружи, как будто её там не существует:
console.log вызывается в глобальном окружении, а x не задан в этом глобальном окружении. Поэтому мы получаем Reference Error.
Мы можем задать x глобально:
Теперь существует глобальный x и его значение было выведено на экран, но локальный x внутри функции multiplier по-прежнему виден только внутри этой функции. Эти два x не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, несмотря на то, что у них одно и то же имя.
Любой блок кода между фигурными скобками имеет локальную область видимости. Вот пример с блоком if :
Ок, локальное не доступно снаружи. Но глобальное доступно везде. Даже внутри чего-то? Да!
Хоть это и заманчиво всё помещать в глобальную область видимости и забыть о сложностях областей видимости — это ужасная практика. Глобальные переменные делают ваш код невероятно хрупким. В таком случае что угодно может сломать что угодно. Поэтому избегайте глобальной области видимости, храните вещи там, где им место.
Часть II. Лексическая область видимости
Взгляните на эту программу:
Весь этот кусок кода мог бы быть внутри другой функции, и ещё внутри другой функции. И если бы b не нашлась здесь, JavaScript продолжил бы искать b за пределами функции, слой за слоем.
Заметьте, что a = 7 не затронула вычисления, a была найдена внутри, поэтому внешняя a не сыграла роли.
Этот механизм называется лексической областью видимости. Область видимости любого компонента определяется местом расположения этого компонента внутри кода. И вложенные блоки имеют доступ к их внешним областям видимости.
Часть III. Замыкания
Большинство языков программирования имеют что-то вроде области видимости или окружения, и этот механизм позволяет существовать замыканиям. Замыкание — это всего лишь модное название функции, которая запоминает внешние штуки, используемые внутри.
Перед тем, как мы продолжим, давайте вспомним, как функции создаются и используются:
f — довольно бесполезная функция, она всегда возвращает 0. Весь этот набор состоит из двух частей: константы и самой функции.
Мы использовали аналогию в предыдущих уроках: константы как листы бумаги — имя на одной стороне, значение на другой. Следовательно, f — лист бумаги с f на одной стороне и описанием запускаемой функции на другой.
Когда вы вызываете эту функцию, вот так:
… создаётся новый ящик, основываясь на описании на этом листе бумаги.
Ок, вернёмся к замыканиям. Рассмотрим следующий код:
Это может выглядеть как JavaScript-фокус, но замыкания, когда их используют разумно, могут сделать код приятней, чище и проще для чтения. И сама идея возврата функций тем же способом, которым можно возвращать числа и строки, даёт больше возможностей и гибкости.
Вы заметите, как часто эти идеи используются в программировании, и мы рассмотрим их мощь в следующих курсах.
Выводы
Глобальная против локальной
Локальные константы и переменные не видимы снаружи их области видимости:
Но если x представлен глобально, то он доступен:
Возможен доступ к внешней области видимости:
Лексическая область видимости
JavaScript пытается найти значение в текущем окружении. Но значение не находится и JavaScript выходит наружу, на один уровень за попытку, пока не найдёт значение или не поймет, что значение невозможно найти.
Замыкания
Это замыкание: сочетание функции и окружения, где она была заявлена.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты.
Нашли опечатку или неточность?
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Что-то не получается или материал кажется сложным?
Загляните в раздел «Обсуждение»:
Об обучении на Хекслете
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Понимание областей видимости или Scope в AngularJS
Наследование областей, как правило, прямое, и часто даже не нужно знать, как оно делается… пока не столкнешься с двухсторонней привязкой данных (т. е. элементами формы, ng-model) к примитивам (напр., числу, строке, логическому типу), определенными в родительской области видимости из дочерней. Она работает не так, как этого ожидают большинство людей. Происходит так, что потомок создает собственную область видимости, которая перекрывает родительское свойство с одноименным названием. Это не особенность Ангуляра, так работает прототипное наследование в Яваскрипте. Новые разработчики, работающие с Ангуляром, часто не понимают, что ng-repeat, ng-switch, ng-view и ng-include создают новые дочерние области, так что проблема появляется при использовании этих директив.
Этой проблемы легко избежать, следуя «лучшим практикам», где говорится, что выражение в ng-model должно всегда содержать точку.
Точка «.» в модели гарантирует, что прототипное наследование работает как надо. Поэтому
лучше чем
.
Если действительно хотите/нужно использовать примитивы, есть два пути решения проблемы:
Прототипное наследование в Яваскрипте
В первую очередь, важно иметь четкое представление о прототипном наследовании в Яваскрипте, особенно если пришли из серверного программирования и больше знакомы с классическим наследованием.
Предположим parentScope имеет свойства aString, aNumber, anArray, anObject, и aFunction. Если childScope прототипически наследуется от parentScope, имеем:
(Обратите внимание, что для экономии места, объект anArray показан как одиночный объект с тремя значениями, а не объект с тремя строками.)
Если попытаться получить доступ к свойствам по умолчанию, определенным в parentScope, из дочерней области видимости, Яваскрипт сначала ищет в дочерней области и, не найдя там, затем смотрит в родительской области, где уже находит свойство. (Если бы не нашел в parentScope, то продолжил бы поиск по цепочке прототипов. вплоть до корневой области видимости). Итак, все утверждения ниже верны:
Предположим, что после этого сделали так:
Цепочка прототипов не расматривается, а новое свойство aString добавляется в childScope. Новое свойство перекрывает свойство из parentScope с таким же названием. Это будет очень важным, при обсуждении ng-repeat и ng-include ниже.
Предположим, что после этого сделали так:
Цепочка прототипов рассматривается, так как объекты (anArray и anObject) не найдены в childScope. Объекты находятся в parentScope, а значения свойств обновляются в исходных объектах. Новые свойства не добавляются в childScope; новые объекты не создаются. (Отметим, что в Яваскрипте массивы и функции также являются объектами.)
Предположим, что после этого сделали так:
Цепочка прототипов опять не рассматривается, и дочерняя область видимости получает два новых свойства объекта, которые перекрывают свойства объекта из parentScope с теми же именами.
Сперва удалим свойство из childScope, а затем, когда попытаемся получить доступ к свойству снова, состоится переход по цепочке прототипов.
Наследование областей видимости в Ангуляре
ng-include
Предположим, что имеем в контроллере:
Каждая ng-include создает новую дочернюю область видимости, прототипически унаследованную от родительской области.
Ввод (скажем, «77») в первом текстовом поле приведет к тому, что дочерняя область видимости получит новое свойство myPrimitive из области видимости, которое перекроет свойство родительской области с одноименным названием.
Вероятно, это не то, чего вы хотите или ожидаете.
Ввод (скажем, «99») во втором текстовом поле не приводит к созданию новой дочерней области видимости. Потому что tpl2.html связывает модель со свойством объекта, прототипное наследование заканчивается, когда ngModel ищет объект MyObject — находит его в родительской области.
Примечание: на изображении выше ошибка, число «99» должно заменить 11, а не 50.
В случаях, когда элементы формы не задействуются, другим решением будет определение функции в родительской области видимости для изменения примитива. Только убедитесь, что потомок всегда вызывает эту функцию, которая будет доступна в дочерней области видимости в соответствии с прототипными наследованием. Например,
Здесь простой пример, который использует подход с «родительской функцией». (Являлся частью поста на Stack Overflow.)
ng-switch
ng-repeat
ng-repeat работает немного по-другому. Предположим, что в контроллере содержится следующее:
Для каждого элемента/итерации, ng-repeat создает новую область видимости, которая прототипически наследуется от родительской, но он также присваивает значение элемента новым свойствам в новой дочерней области. (Названием нового свойства является имя переменной из цикла.) Ниже показан фактический исходный код для ng-repeat:
ng-repeat не будет работать (как хотите или ожидаем). В Angular 1.0.2 или более раннем, ввод в поля ввода, изменяет значения в серых блоках, которые видны только в дочерних областях. В Angular 1.0.3 +, ввод в текстовые поля никак на них не влияет. (См. пояснения Артема, почему это так.) Мы же хотим, чтобы ввод влияюл на массив myArrayOfPrimitives, а не на свойство дочерней области видимости. Чтобы достичь этого, необходимо заменить модель на массив объектов.
(серая линия показывает куда он переходит.)
Это работает, как ожидалось. Ввод в поля ввода, изменяет значения в серой области, которые видны и в дочерней и в родительской областях видимости.
ng-view
Уточняется, но думаю, что действует так же, как ng-include.
ng-controller
(Если действительно необходимо поделиться данными через наследование области видимости контроллеров, то делать ничего не надо. Дочерняя область видимости будет иметь доступ ко всем свойствам в родительской области. См. также Controller load order differs when loading or navigating)
Диретивы
Хэш-объект используется для создания двухсторонней привязки (с помощью «=») или односторонней (с помощью «@») между родительской областью и изолированной. Есть так же «&» для привязки к выражениям в родительской области. Таким образом, все они создают локальные свойства области видимости, получаемые из родительской области. Обратите внимание, что атрибуты используются чтобы помочь установить
привязки — невозможно сослаться на имена свойств из родительской области в хэш-объекте, без использования этих атрибутов. Например, привязка к родительскому свойству parentProp в изолированной области: и
Для рисунка ниже имеем
Предположим так же, что директива делает это в связующей функции: scope.someIsolateProp = «I’m isolated»
Для рисунка ниже имеем, такую же директиву как указано выше с этим дополнением: transclude: true
Этот пример содержит функцию showScope() которую можно использовать для изучения изолированной области видимости и ассоциированной с ней включенной области. Смотрите инструкции в комментариях к примеру.
Заключение
Существует четыре типа областей видимости: