Стек — это структура данных, представляющая собой список элементов, организованных по принципу LIFO (от англ. last in — first out, «последним пришёл — первым вышел»).
Стек можно представить в виде детской пирамидки: чтобы извлечь второе сверху кольцо, нужно сначала снять самое верхнее.
Уже знакомые Вам методы push и pop позволяют работать с массивом как со стеком:
var stack = [];
stack.push('Tom');
stack.push('Jane');
stack.push('Bill');
stack.pop(); // Bill
stack.pop(); // Jane
stack.pop(); // TomКакое отношение стек имеет к функциям?
function a() { b() }
function b() { c() }
function c() { d() }
function d() { console.log('D') }Поскольку функции могут вызывать друг друга, им необходима информация о том, в какую часть программы следует вернуться после завершения.
Пусть некоторый вызов из предыдущего примера привел нас в функцию d.
function d() {
console.log('D');
// Выпонение завершено, куда возвращаемся?
}Что следует выполнять после завершения функции? Для ответа на этот вопрос существует стек вызовов.
Стек вызовов — это специальная область памяти, которая содержит информацию, необходимую для возврата управления из подпрограммы в место вызова.
Стек является «клубком ниток», который помогает среде исполнения найти дорогу в «лабиринте вызовов».
Записи в стеке вызовов называют фреймами (от англ. frame — кадр).
Вызываем a().
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ →a();
В стек записывается <main>:6 (адрес, откуда сделан вызов).
Находимся в a:1, вызываем b().
/*1*/ function a() { →b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1
Находимся в b:2, вызываем c().
/*1*/ function a() { b() }
/*2*/ function b() { →c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1, b:2
Находимся в c:3, вызываем d().
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { →d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1, b:2, c:3
Находимся в d:4, вызываем console.log().
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { →console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1, b:2, c:3, d:4
Вернулись из console.log в d:4.
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') → }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1, b:2, c:3
Вернулись в c:3.
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() → }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1, b:2
Вернулись в b:2.
/*1*/ function a() { b() }
/*2*/ function b() { c() → }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6, a:1
Вернулись в a:1.
/*1*/ function a() { b() → }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a();
Стек: <main>:6
Вернулись в главную программу.
/*1*/ function a() { b() }
/*2*/ function b() { c() }
/*3*/ function c() { d() }
/*4*/ function d() { console.log('D') }
/*5*/
/*6*/ a(); →
Стек пуст.
Используйте console.trace(), чтобы увидеть стек вызовов в определенный момент работы программы.
function a() { b() }
function b() { c() }
function c() { d() }
function d() { console.trace() }
Используйте трассировку для отладки Ваших программ, но с осторожностью.
Стек вызовов может содержать избыточную информацию, которая существенно усложняет отладку.