awesome-everything EN
↑ Обратно к восхождению

Базовый CS с нуля

Область видимости

Суть Область видимости — это какие имена доступны в данной точке кода. Локальные переменные живут в своём кадре и исчезают при его снятии. Время жизни имени напрямую связано со временем жизни его кадра.
◷ 18 min

Ты знаешь, что каждый вызов функции получает собственный кадр стека и что кадр снимается при возврате функции. Но что это означает для имён, которые ты используешь в коде? Может ли одна функция видеть переменные другой? Можно ли использовать имя до его объявления? Что происходит с переменной после возврата функции?

На все эти вопросы отвечает одна концепция: область видимости. Область видимости — правило, определяющее, какие имена видны в данной точке кода. В большинстве языков, включая TypeScript, область видимости напрямую связана со структурой функций. Переменная, объявленная внутри функции, видна только внутри этой функции — и перестаёт существовать, когда кадр функции снимается.

Цель

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

1

Область видимости: регион, где имя видимо. Каждое имя (переменная, параметр, имя функции) видно в какой-то части кода и невидимо в остальной. Регион, где имя можно использовать, называется его областью видимости. За пределами области видимости имя не существует с точки зрения компилятора — обращение к нему является ошибкой.

В TypeScript (и большинстве языков семейства C) область видимости определяется фигурными скобками { }. Имя, объявленное внутри блока { ... }, ограничено этим блоком: оно видно от точки объявления до закрывающей } и невидимо снаружи. Тело функции — это блок, поэтому переменная, объявленная внутри функции, ограничена этой функцией.

2

Локальная область видимости: переменные, объявленные внутри функции. Переменная, объявленная внутри тела функции, называется локальной переменной. Её область видимости — тело функции: от её объявления до конца функции. Никакой код за пределами функции не может видеть эту переменную по имени.

Рассмотрим две функции f и g, каждая из которых объявляет переменную с именем x:

function f() { let x = 1; }
function g() { let x = 2; }

Это две совершенно разные переменные, каждая ограниченная своей функцией. Одинаковое имя x не вызывает конфликта: каждый x указывает на отдельную ячейку в отдельном кадре стека. Когда выполняется f, её x — ячейка в кадре f; когда выполняется g, её x — другая ячейка в кадре g.

3

Время жизни: как долго переменная существует. Область видимости — это где в коде видимо имя. Время жизни — это как долго во времени переменная существует в памяти. Для локальных переменных время жизни напрямую связано с кадром:

  • Переменная появляется, когда кадр кладётся (вызывается функция).
  • Переменная уничтожается, когда кадр снимается (функция возвращается).

Нет способа сохранить ссылку на локальную переменную после возврата функции: ячейки кадра немедленно переиспользуются для следующего вызова. Попытка использовать такую ссылку означала бы чтение из ячеек, которые теперь принадлежат кому-то другому, — класс ошибок, называемый «использование после освобождения» или «висячая ссылка».

Почему это работает

Зачем связывать время жизни переменной с кадром, а не позволять переменным существовать бесконечно? Потому что ключевое преимущество стека — бесплатное автоматическое управление: положить и снять кадр — это одна арифметическая операция над указателем стека. Если бы локальные переменные могли переживать свои кадры, рантайму пришлось бы отслеживать их по отдельности — по сути, изобретая кучу заново. Правило «время жизни = время жизни кадра» делает выделение памяти под локальные переменные очень дешёвым. Когда нужно, чтобы значение пережило создавшую его функцию, оно явно помещается в кучу (через объекты, массивы или выделение) — и тогда платится бо́льшая цена управления кучей.

4

Почему g не видит локальные переменные f. Когда выполняется g, кадр f может вообще отсутствовать на стеке (если g была вызвана независимо, а не из f). Даже если f вызвала g и кадр f всё ещё находится ниже кадра g на стеке, правила области видимости не позволяют g обращаться к локальным переменным f по имени: компилятор этого не допускает. Это не просто ограничение компилятора — оно отражает модель памяти. Локальные переменные f — ячейки в кадре f, который существует по конкретному диапазону адресов в области стека. У g нет именованного способа достичь этих адресов.

Эта изоляция — достоинство, а не ограничение. Она означает, что писать g можно, не беспокоясь о том, какие имена объявила f. Каждая функция — закрытое, самодостаточное рабочее пространство.

main a=1
frame₀
f x=10
frame₁
g x=20
frame₂
Три кадра на стеке. У каждой функции своя переменная x. g не может назвать x функции f — это разные ячейки по разным адресам, и правила области видимости запрещают доступ к именам из чужого кадра.
Разбор примера

Трассировка области видимости через последовательность вызовов.

function add(a: number, b: number): number {
  let sum = a + b;  // sum локальна для add, область видимости = тело add
  return sum;
}

function main(): void {
  let result = add(3, 4);  // result локальна для main
  // sum здесь не существует — она вне области видимости
}

Пока выполняется add:

  • a, b, sum находятся в области видимости — это ячейки в кадре add.
  • result не в области видимости здесь (она в кадре main, и правила области видимости запрещают доступ к именам из чужого кадра).

После возврата add:

  • Кадр add снят. a, b и sum больше не существуют.
  • Обратно в main: result в области видимости (она в кадре main). Имя sum вне области видимости — обращение к нему было бы ошибкой компиляции.

Компилятор применяет правила области видимости во время компиляции: если написать sum в теле main, TypeScript сообщит об ошибке — «Не удаётся найти имя “sum”». Ячейки, которые хранили sum, физически всё ещё находятся в памяти до переиспользования следующим вызовом, но имя больше ни на что не указывает.

Частая ошибка

Распространённая ошибка — думать, что две функции, использующие одно и то же имя переменной, делят одну ячейку памяти. Это не так. Каждый вызов функции создаёт собственный кадр; каждый кадр имеет собственные именованные ячейки. let x = 1 в функции f и let x = 2 в функции g создают две разные ячейки, каждая с именем x в своей области видимости. Изменение x в f не влияет на x в g — они находятся по разным адресам.

Практика 0 / 5

function f() { let x = 5; } function g() { /* может ли g использовать x здесь? введи 1 за да, 0 за нет */ }

function f() { let x = 5; return x; } — После возврата f, существует ли x в памяти как живая именованная ячейка? Введи 1 за да, 0 за нет.

function f() { let x = 1; } function g() { let x = 2; } — Обе функции объявляют переменную с именем x. Сколько различных ячеек памяти с именем x существует, когда обе функции активны?

Функция вызывается 3 раза подряд (не вложенно). Каждый вызов объявляет локальную переменную y. Сколько раз y создаётся и уничтожается?

f вызывает g. Пока выполняется g, кадр f всё ещё на стеке. Может ли g получить доступ к локальной переменной q функции f по имени? Введи 1 за да, 0 за нет.

Проверь себя
Викторина

Что происходит с локальными переменными функции при её возврате?

Итог

Область видимости — участок кода, где видимо имя. В TypeScript переменная, объявленная внутри тела функции, имеет локальную область видимости: она видна только внутри этой функции, от её объявления до закрывающей скобки. Время жизни локальной переменной связано со временем жизни её кадра — она создаётся при кладке кадра (вызове функции) и уничтожается при его снятии (возврате функции). Две функции могут объявлять переменные с одинаковым именем без конфликта, потому что каждая переменная живёт в собственном кадре по собственному адресу. Ни одна функция не может обращаться к локальным переменным другой функции по имени — правила области видимости, применяемые компилятором, это запрещают, отражая лежащую в основе модель памяти, где каждый кадр является самодостаточным блоком ячеек.

Продолжить восхождение ↑Предварительный взгляд на рекурсию
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.