← Назад к вопросам

Как функция видит внешние переменные?

2.0 Middle🔥 131 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм доступа функции к внешним переменным

Функции в JavaScript получают доступ к внешним переменным благодаря механизму лексического окружения (Lexical Environment) и цепочке областей видимости (Scope Chain). Это фундаментальный принцип работы замыканий (closures), который напрямую связан с тем, как и где функция была объявлена, а не вызвана.

Лексическое окружение и цепочка областей видимости

Каждая выполняемая функция, блок кода и скрипт в целом имеют связанный с ними внутренний, "скрытый" объект — лексическое окружение. Этот объект состоит из двух частей:

  1. Environment Record — хранилище, где содержатся все локальные переменные и параметры функции.
  2. Ссылка на внешнее лексическое окружение (Outer Lexical Environment Reference, или просто [[Environment]]).

Когда код хочет получить доступ к переменной, он в первую очередь ищет её в собственном лексическом окружении функции. Если переменная не найдена, поиск продолжается во внешнем лексическом окружении, на которое ссылается функция. Этот процесс повторяется по цепочке ссылок, пока переменная не будет найдена или пока цепочка не закончится (в глобальном окружении).

// Глобальное лексическое окружение
const globalVar = 'Я глобальная';

function outer() {
    // Лексическое окружение функции outer()
    const outerVar = 'Я из outer';

    function inner() {
        // Лексическое окружение функции inner()
        const innerVar = 'Я из inner';
        console.log(innerVar); // 1. Ищет здесь -> находит
        console.log(outerVar); // 2. Не находит здесь -> ищет в outer() -> находит
        console.log(globalVar); // 3. Не находит здесь -> ищет в outer() -> не находит -> ищет в глобальном -> находит
    }

    return inner;
}

const myInner = outer();
myInner(); // Успешно выводит все три переменные

Ключевой момент: ссылка на внешнее окружение ([[Environment]]) фиксируется в момент создания функции (на этапе инициализации скрипта или выполнения родительской функции). Поэтому функция inner навсегда "запоминает" лексическое окружение функции outer, в котором она была рождена, даже если outer уже завершила своё выполнение. Именно это и есть замыкание.

Практические аспекты и примеры

1. Доступ к переменным родительской функции после её завершения

Это классический пример замыкания, который широко используется для создания приватных переменных.

function createCounter() {
    // Эта переменная находится в лексическом окружении createCounter
    // Она НЕ доступна напрямую извне, но доступна для вложенных функций.
    let count = 0;

    // Функции increment и decrement "замыкают" переменную count.
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getValue: function() {
            return count;
        }
    };
}

const counter = createCounter();
// Переменная count существует только внутри замыкания.
console.log(counter.getValue()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
// console.log(count); // ReferenceError: count is not defined (переменная приватная)

2. Разное поведение var, let/const и циклы

Из-за блочной области видимости let/const и отсутствия таковой у var возникают классические различия в поведении.

function testVar() {
    var funcs = [];
    for (var i = 0; i < 3; i++) {
        // Переменная i одна на весь цикл (функциональная область видимости).
        funcs.push(function() {
            console.log(i); // Все функции видят одно итоговое значение i = 3
        });
    }
    return funcs;
}

const varFuncs = testVar();
varFuncs[0](); // 3
varFuncs[1](); // 3
varFuncs[2](); // 3
function testLet() {
    const funcs = [];
    for (let i = 0; i < 3; i++) {
        // Для каждой итерации цикла создаётся НОВОЕ лексическое окружение
        // со своей собственной переменной i.
        funcs.push(function() {
            console.log(i); // Каждая функция видит своё значение i
        });
    }
    return funcs;
}

const letFuncs = testLet();
letFuncs[0](); // 0
letFuncs[1](); // 1
letFuncs[2](); // 2

3. Стрелочные функции и this

Стрелочные функции используют лексическое окружение для определения значения this. Они не имеют своего собственного this, arguments, super или new.target.

const obj = {
    name: 'Мой объект',
    regularFunc: function() {
        console.log(this.name); // 'this' зависит от вызова
    },
    arrowFunc: () => {
        console.log(this.name); // 'this' берется из внешнего лексического окружения (здесь - глобального)
    }
};

obj.regularFunc(); // 'Мой объект' (this = obj)
obj.arrowFunc();   // '' или undefined (this = window/global)

// Полезный паттерн: использование стрелочной функции для сохранения контекста
function Timer() {
    this.seconds = 0;
    // Стрелочная функция замыкает 'this' из лексического окружения конструктора Timer.
    setInterval(() => {
        this.seconds++; // 'this' корректно указывает на экземпляр Timer
        console.log(this.seconds);
    }, 1000);
}

const timer = new Timer();

Итог и важные выводы

  • Основа доступа: Функция видит внешние переменные благодаря лексическому окружению и цепочке областей видимости, которая формируется в момент её создания.
  • Замыкание (Closure): Это комбинация функции и лексического окружения, в котором она была объявлена. Это позволяет функции сохранять доступ к переменным внешней функции даже после её завершения, что является основой для приватных переменных и фабричных функций.
  • Область видимости (Scope): Важно понимать разницу между функциональной (var) и блочной (let/const) областью видимости, особенно в циклах и асинхронных операциях.
  • this в стрелочных функциях: Значение this в стрелочной функции определяется лексическим окружением, в котором она была создана, а не способом вызова.

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

Как функция видит внешние переменные? | PrepBro