Как движок JS ищет ссылку на объявленную переменную?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как движок 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();
Порядок поиска:
- Local Scope - переменные в текущей функции
- Lexical Scopes - переменные в функциях-родителях
- Global Scope - переменные в глобальном контексте
- 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':
- Проверяем Variable Environment текущего контекста
- Найдено! Возвращаем 'C'
Поиск 'a':
- Проверяем Variable Environment текущего контекста
- Не найдено
- Проверяем Parent Lexical Environment
- Найдено! Возвращаем '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создаёт функциональный scopelet/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'
Алгоритм поиска:
- Ищем x в inner scope - НАЙДЕНО 'inner'! Используем это.
- Не идём дальше в цепочке
Это называется shadowing - переменная из inner scope "затеняет" переменные с тем же именем в outer scopes.
9. Hoisting - предварительный проход
Перед выполнением кода движок делает проход парсинга, где:
- Все функции объявлены полностью
varпеременные создаются с значениемundefinedlet/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:
- Lexical Scope - определяется ВГД код написан
- Scope Chain - цепочка от текущего до global scope
- Variable Environment - хранит локальные переменные
- Closure - функция помнит свой lexical scope
- Shadowing - может скрыть переменные из outer scope
- Hoisting - предварительный проход перед выполнением
Понимание этих механизмов критично для отладки, оптимизации и написания безошибочного JavaScript кода.