The executable stack becomes empty when all code is run.
how can we still access global variables?
Even when the code does not execute, the global lexical environment with the global object still exists. When you load some code into the chrome console, the code is evaluated, a new global context is created that is created and initialized with its lexical and variable environments set to the global environment, and this bound to the global object. Then your code executes in this context, and the executable stack is empty again.
how does this still point to a global object?
Each time a new global execution context is initialized with global code, this bound to the global object.
in the case of asynchronous callbacks, when an event leaves the event queue and falls into the JS engine to run, what exactly happens in the execution stack?
Again, a new global execution context is created and pushed onto the empty execution stack. In MDN, this is described in slightly different terms than in the ECMAScript specification :
When the stack is empty, the message is unloaded from the queue and processed. Processing consists of calling a related function (and thus creating the original stack frame). Message processing ends when the stack becomes empty again. ( MDN. Concurrency model and event loop )
Here, “stack frame” means “execution context” and “initial stack frame” corresponds to the “global execution context”.
Is the callback execution context located at the bottom of this stack or the global execution context still exist?
None of them. The stack is empty. And only if it is empty, the oldest callback is taken from the callback / event queue:
When there is no executable execution context and the execution context stack is empty, the ECMAScript implementation removes the first PendingJob from the job queue and uses the information contained in it to create the execution context and starts the execution of the associated operation. ECMAScript 6.0 Specification