The JavaScript engine uses a stack 'Execution Context Stack', 'Runtime Stack', or 'Machine Stack'.
It uses the LIFO principle (Last-In-First-Out). When the engine first starts executing the script, it creates a global context and pushes it on the stack. Whenever a function is invoked, the JS engine creates a function stack context for the function pushes it to the top of the call stack and starts executing it.
When execution of the current function is complete, then the JavaScript engine will automatically pop the context from the call stack and it goes back to its parent.
Primitives: Simple values like numbers ($5$) or booleans (true) are usually stored directly inside the Execution Context on the Stack because their size is fixed and known.
Objects/Arrays: Because these can grow or shrink, they are stored in the Heap. The Execution Context on the Stack only holds a pointer (a memory address) that tells the engine where to find that object in the Heap.
Closures (The Exception): If a function 'remembers' a variable from its parent scope (a closure), that variable can't be cleared when the parent function finishes. In this case, the JavaScript engine moves those specific variables from the Stack to the Heap so they don't disappear when the Execution Context is popped off the stack.