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

Как движок JS ищет ссылку на объявленную переменную?

2.0 Middle🔥 151 комментариев
#JavaScript Core#Браузер и сетевые технологии

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Как движок JS ищет ссылку на объявленную переменную

Поиск переменных в JavaScript - это фундаментальный процесс, который называется scope resolution или variable lookup. Движок выполняет это через механизм scope chain (цепочка областей видимости).

1. Механизм Scope Chain

Когда движок ищет переменную, он проходит последовательно по цепочке scopes:

const global = 'global';

function outer() {
  const outerVar = 'outer';
  
  function inner() {
    const innerVar = 'inner';
    
    console.log(innerVar);  // Найдёт в inner scope
    console.log(outerVar);  // Найдёт в outer scope
    console.log(global);    // Найдёт в global scope
  }
  
  inner();
}

outer();

Порядок поиска:

  1. Local Scope - переменные в текущей функции
  2. Lexical Scopes - переменные в функциях-родителях
  3. Global Scope - переменные в глобальном контексте
  4. Not Found - ReferenceError

2. Пример поиска переменной

// Шаг 1: Определение переменных
var globalVar = 'global';

function level1() {
  var level1Var = 'level1';
  
  function level2() {
    var level2Var = 'level2';
    
    function level3() {
      var level3Var = 'level3';
      
      // Ищем 'unknown'
      console.log(unknown); // ?
    }
    
    level3();
  }
  
  level2();
}

level1();

Процесс поиска 'unknown':

1. Ищем в level3 scope? НЕТ
2. Ищем в level2 scope (parent)? НЕТ
3. Ищем в level1 scope (grandparent)? НЕТ
4. Ищем в global scope? НЕТ
5. ReferenceError: unknown is not defined

3. Внутренние свойства - [[Scope]]

Каждая функция имеет внутреннее свойство [[Scope]], которое ссылается на все родительские scopes:

const x = 'global';

function outer() {
  const y = 'outer';
  
  function inner() {
    console.log(y);
  }
  
  // inner.[[Scope]] содержит ссылки на:
  // 1. { y: 'outer' } <- outer's scope
  // 2. { x: 'global' } <- global scope
  
  return inner;
}

const myFunc = outer();
myFunc(); // 'outer'

// Даже после завершения outer(), inner помнит y
// благодаря [[Scope]]!

Это создаёт closure (замыкание).

4. Execution Context и Scope Chain

Когда функция выполняется, создаётся Execution Context с тремя компонентами:

Execution Context {
  Variable Environment: { /* локальные переменные */ }
  Lexical Environment: { /* из которого вызвана функция */ }
  Scope Chain: [Variable Env] -> [Parent 1] -> ... -> [Global]
}

Пример:

var a = 'A';

function func1() {
  var b = 'B';
  func2();
}

function func2() {
  var c = 'C';
  console.log(a); // поиск в scope chain
}

func1();

Когда выполняется func2():

func2's Execution Context {
  Variable Environment: { c: 'C' }
  Scope Chain: [{ c }] -> [{ a }] (global)
}

5. Порядок поиска конкретный

Поиск 'c':

  1. Проверяем Variable Environment текущего контекста
  2. Найдено! Возвращаем 'C'

Поиск 'a':

  1. Проверяем Variable Environment текущего контекста
  2. Не найдено
  3. Проверяем Parent Lexical Environment
  4. Найдено! Возвращаем 'A'

6. var vs let vs const

Различия в определении scope:

// var - function scope
function test() {
  for (var i = 0; i < 3; i++) {}
  console.log(i); // 3 (i видна во всей функции)
}

// let/const - block scope
function test2() {
  for (let j = 0; j < 3; j++) {}
  console.log(j); // ReferenceError! j видна только в блоке
}

Почему?

  • var создаёт функциональный scope
  • let/const создают блочный scope (более точный поиск)

7. Closure - сохранение Scope Chain

function makeCounter() {
  let count = 0; // переменная в outer scope
  
  return function() {
    count++; // inner функция имеет доступ к count
    return count;
  };
}

const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3

// count остаётся в памяти благодаря Scope Chain closure

Движок не удаляет count из памяти, потому что возвращённая функция всё ещё ссылается на него через [[Scope]].

8. Shadowing (затенение переменных)

const x = 'global';

function outer() {
  const x = 'outer'; // Такое же имя!
  
  function inner() {
    const x = 'inner'; // И ещё одно!
    console.log(x); // Какой x?
  }
  
  inner();
}

outer(); // 'inner'

Алгоритм поиска:

  1. Ищем x в inner scope - НАЙДЕНО 'inner'! Используем это.
  2. Не идём дальше в цепочке

Это называется shadowing - переменная из inner scope "затеняет" переменные с тем же именем в outer scopes.

9. Hoisting - предварительный проход

Перед выполнением кода движок делает проход парсинга, где:

  • Все функции объявлены полностью
  • var переменные создаются с значением undefined
  • let/const переменные помещаются в "Temporal Dead Zone"
console.log(x); // undefined (не ошибка!)
var x = 5;

// Выше видит движок как:
// var x;        (hoisted)
// console.log(x); (undefined)
// x = 5;


console.log(y); // ReferenceError!
let y = 5; // Temporal Dead Zone до этой строки

10. Dynamic vs Lexical Scope

JavaScript использует lexical (static) scoping:

const a = 'global';

function outer() {
  const a = 'outer';
  inner();
}

function inner() {
  console.log(a); // Какой 'a'?
}

outer(); // 'global', не 'outer'!

Почему?

Потому что inner скомпилирована в глобальном scope, поэтому её [[Scope]] ссылается на global scope, а не на outer's scope.

11. Практический пример - Network Request

function fetchUser(userId) {
  let user = null; // outer scope
  
  fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(data => {
      user = data; // Ищет user в outer scope (closure)
    });
  
  return user; // null! (асинхронный код не выполнен)
}

Callback закрывает переменную user из outer scope.

12. Оптимизация движками

Современные движки (V8, SpiderMonkey) оптимизируют поиск:

  • Inline Caches (IC) - кешируют результаты lookups
  • Shape Tracking - отслеживают структуру объектов
  • Speculative Optimization - предполагают типы переменных
// После первого выполнения движок кеширует как найти 'x'
function lookup(obj) {
  return obj.x; // Быстро после первого раза
}

for (let i = 0; i < 1000000; i++) {
  lookup({ x: i });
}

Выводы

Поиск переменной в JavaScript:

  1. Lexical Scope - определяется ВГД код написан
  2. Scope Chain - цепочка от текущего до global scope
  3. Variable Environment - хранит локальные переменные
  4. Closure - функция помнит свой lexical scope
  5. Shadowing - может скрыть переменные из outer scope
  6. Hoisting - предварительный проход перед выполнением

Понимание этих механизмов критично для отладки, оптимизации и написания безошибочного JavaScript кода.

Как движок JS ищет ссылку на объявленную переменную? | PrepBro