Scope

Scope (область видимости) — контекст, определяющий доступность переменных и функций в конкретной точке кода.

Зачем нужно

Scope предотвращает конфликты имён, защищает данные от случайного изменения и определяет время жизни переменных. Понимание scope — ключ к пониманию замыканий и модульности.

Где используется

  • Определение доступности переменных
  • Замыкания (closures)
  • Модульная система
  • Управление памятью (сборка мусора)

Предпосылки

Переменные, Условия

Виды scope

Global scope (глобальная)

// Переменные, объявленные вне функций/блоков
const APP_NAME = 'MyApp'; // доступна везде

// var создаёт свойство window
var globalVar = 'hello';
console.log(window.globalVar); // 'hello' (в браузере)

// let/const НЕ создают свойство window
let globalLet = 'world';
console.log(window.globalLet); // undefined

Function scope (функциональная)

function greet() {
  var message = 'Привет'; // доступна только внутри greet
  let name = 'Алиса';     // тоже только внутри
  console.log(message, name);
}

greet;
// console.log(message); // ReferenceError
// console.log(name);    // ReferenceError

Block scope (блочная)

if (true) {
  let blockLet = 'видна только в блоке';
  const blockConst = 'тоже только в блоке';
  var blockVar = 'видна за пределами блока!';
}

// console.log(blockLet);   // ReferenceError
// console.log(blockConst); // ReferenceError
console.log(blockVar);      // 'видна за пределами блока!' — var игнорирует блоки

// Циклы создают блок
for (let i = 0; i < 3; i++) {
  // i доступна только здесь
}
// console.log(i); // ReferenceError

Lexical scope (лексическая область видимости)

// Scope определяется тем, ГДЕ функция ОБЪЯВЛЕНА, а не где вызвана
const outer = 'внешняя';

function outerFn() {
  const middle = 'средняя';

  function innerFn() {
    const inner = 'внутренняя';
    console.log(inner);  // ОК — своя переменная
    console.log(middle); // ОК — из родительского scope
    console.log(outer);  // ОК — из глобального scope
  }

  innerFn;
  // console.log(inner); // ReferenceError — нет доступа вниз
}

outerFn;

Scope chain (цепочка областей видимости)

const a = 'global';

function first() {
  const b = 'first';

  function second() {
    const c = 'second';

    function third() {
      const d = 'third';
      // Поиск переменной идёт вверх по цепочке:
      // third → second → first → global
      console.log(d); // 'third' (нашли в своём scope)
      console.log(c); // 'second' (поднялись на 1 уровень)
      console.log(b); // 'first' (поднялись на 2 уровня)
      console.log(a); // 'global' (поднялись на 3 уровня)
    }
    third;
  }
  second;
}
first;

Замыкания (Closures)

// Функция «помнит» своё окружение
function createCounter() {
  let count = 0; // приватная переменная

  return {
    increment { return ++count; },
    decrement { return --count; },
    getCount { return count; }
  };
}

const counter = createCounter;
counter.increment; // 1
counter.increment; // 2
counter.decrement; // 1
counter.getCount;  // 1
// count недоступен напрямую!

// Практический пример: фабрика функций
function multiply(factor) {
  return (number) => number * factor;
}

const double = multiply(2);
const triple = multiply(3);

double(5); // 10
triple(5); // 15

Частые ошибки

1. var в цикле с замыканием

for (var i = 0; i < 3; i++) {
  setTimeout( => console.log(i), 100);
}
// 3, 3, 3 — все ссылаются на одну переменную var

// Решение 1: let
for (let i = 0; i < 3; i++) {
  setTimeout( => console.log(i), 100);
}
// 0, 1, 2

// Решение 2: IIFE (до ES6)
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout( => console.log(j), 100);
  })(i);
}
// 0, 1, 2

2. Случайные глобальные переменные

function oops() {
  mistake = 'Я глобальная!'; // без let/const/var — глобальная!
}
oops;
console.log(window.mistake); // 'Я глобальная!'

// strict mode предотвращает это
'use strict';
function safe() {
  // mistake = 'error'; // ReferenceError!
}

3. Shadowing (затенение)

const name = 'Глобальное имя';

function greet() {
  const name = 'Локальное имя'; // Затеняет глобальное
  console.log(name); // 'Локальное имя'
}

greet;
console.log(name); // 'Глобальное имя'

Практика

  1. Создай функцию-счётчик с замыканием (increment, decrement, reset)
  2. Напиши фабрику функций, которая создаёт приветствие на разных языках
  3. Покажи разницу var/let в цикле с setTimeout
  4. Создай модуль с приватными переменными через IIFE

Связанные темы

Ресурсы


🎓 Источник: 👶 JavaScript для начинающих 6. Функции и область видимости

  • 📅 2021-10-02 · YouTube
  • Тезисы: scope = окружение для разрешения идентификаторов. Лексический скоуп = по тексту программы. Каждая функция создаёт новый scope. Глобальный, function, block

⚡ Источник: JavaScript and the Lexical Environment · AsForJS

  • 📅 2023-05-12 · YouTube
  • Тезисы:
    • В спеке нет "scope chain", есть Environment Record со ссылкой [[OuterEnv]]
    • При обращении к идентификатору движок резолвит цепочку Environment Records снизу вверх до глобального
    • Lexical Environment ≠ Variable Environment (последний только для var)

⚡ Источник: ⎡03⎦ Performance of JavaScript variables identifiers · AsForJS

  • 📅 2023-06-20 · YouTube
  • Тезисы:
    • Доступ к переменной из внешнего scope медленнее, чем к локальной (нужно идти по [[OuterEnv]])
    • V8 оптимизирует через context slot indexes, но через несколько уровней вложенности замедление заметно
    • with и eval полностью убивают оптимизации scope