When does a function get added to the call stack? - javascript

Lets say we have a sequence of functions being executed in the global scope, like so:
function second() {}
function first() {
second();
}
first();
When does first get added to the call stack?
Does it get added upon invocation, or does it get added after it calls second (and the execution context is now inside of second) ?

A function is "added to the stack" when it is called. While a "call stack" implementation is not specified in ECMAScript 5th edition it does define the virtual behavior in 10.3 Execution Contexts:
When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context [ie. currently running function]. A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context. The newly created execution context is pushed onto the stack and becomes the running execution context.
It is not technically the function that is part of the stack, but rather the execution context created from invoking the function.
This also agrees with more general Call Stack concept where the active/current function context is also part of the stack.
In computer science, a call stack is a stack data structure that stores information about the active subroutines [including the subroutine currently running] of a computer program .. the details are normally hidden and automatic in high-level programming languages..
Using this definition also ensures a function is never running "off the stack" - which agrees with such a concept from other languages/environments and JavaScript developer tools.

function second() {/* 3rd code here will be executed once second is called */}
function first() {
// 2nd code here will be executed before second(); is called
second();
// 4th code here will be executed after second(); is called and all its code has been executed
}
// 1st code here will be executed before anything is called
first();
// 5th code here will be executed when everything has been called.

Related

Behavior of Global Execution Context wrt setTimeout

Let's say I have the following piece of code
function perpetuity() {
console.log("Being called");
setTimeout(perpetuity, 1500);
}
perpetuity();
I would like to know if the global execution context is present in the call stack so that this code can be executed.
OR
If the global execution context is created and deleted along with the callback function's execution context every 1500ms.
Chat gpt says GEC never stops but I cannot find the GEC in my browser's call stack when executing this.
There is no "global execution context". (I assume you're not confusing this with the global environment record?)
There is however a realm execution context for all js code running in a browser. It is not actually useful for anything and more of a technicality (to allow tracking the realm that caused the execution), but it is pushed to the execution context stack before running any JS code and popped afterwards. It is created in InitializeHostDefinedRealm together with the intrinsics, the global object and the global environment of the realm, to create the realm.

Extra execution context in async functions

When I was reading spec I saw next part:
NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently
executing context.
I don't understand this. Why do we need to copy execution context? Can't we do it without extra execution context or what will be broken without copying in that case?
Evaluation of an async function body happens in a separate execution context that can be repeatedly resumed and suspended. The algorithm steps executed in this context are given in AsyncBlockStart #3.
On await (in Await #8) and completion (i.e. return/throw, in AsyncBlockStart #3.g), the execution context is popped off the stack (and in case of await, suspended to resume where it left off, in Await #9).
On promise fulfillment/rejection (in Await #3.c/5.c) and when starting the async function (in AsyncBlockStart #4), it is pushed onto the stack and resumed.
These push/pop operations need to symmetrically correspond to each other, both when starting and resuming the code it may run into either a suspension or the end of the code; and in all four cases the stack must have the same running execution context on top before and after.
In case of a resumption from promise settlement, that running execution context will be the current promise job. In case of AsyncFunctionStart, that running execution context will be the one created and pushed by the PrepareForOrdinaryCall steps during the [[Call]] to the async function (which goes through OrdinaryCallEvaluateBody, EvaluateBody to EvaluateAsyncFunctionBody which creates the promise and performs AsyncFunctionStart). It will afterwards be popped from the stack in [[Call]] #7 like for any other function.
So why do we need an extra execution context? Because if we didn't create a new one (as a copy of the current), it would have been popped off already when AsyncFunctionStart ends, and [[Call]] would fail to pop it again. (Or worse, pop one too many). Of course, an alternative solution to this problem would have been to not make a copy of the current execution context, reuse the suspendable execution context instead, and just push it again onto the stack (without resuming it, only setting it as the running execution context) after the AsyncBlockStart in AsyncFunctionStart #4. But that would've been weird, no?
After all, it doesn't matter which way it's specified, the outcome would be the same. The execution context is not observable from user code.
Note: re-using the same execution context is in fact what generators do. GeneratorStart #2 (which is called from EvaluateGeneratorBody, where the parameter declarations are evaluated and the Generator instance is created) does use the running execution context as the genContext that is repeatedly resumed and suspended. The main difference is that the start ("first resumption") doesn't already happen during the function call for generators (as it does happen for async functions), it will only happen later in the first next() call.
And actually "It is ill-defined to resume a currently executing context." doesn't apply here. The currently executing context would get implicitly suspended in AsyncBlockStart #3 by setting "the code evaluation state of asyncContext such that when evaluation is resumed […]", just like it does happen in GeneratorStart #4.

The callstack is a model that describes how functions are processed. Is there a similar model for variable assignment, if statements & loops?

This video talks about a callstack that functions get 'pushed onto' and 'popped off' of.
If a method in the stack takes a long time, to complete, it can cause blocking - new DOM rendering pauses and sometimes the rendering actions get backlogged and execute after any lengthy functions have finished executing.
What about things like assigning variables, if statements and loops? Are they not potentially time-consuming operations that could cause blocking as described here? Is there a model for these sorts of operations like there is for functions?
You've misunderstood. Javascript is single-threaded, so everything that happens in client-side JavaScript (excluding web workers) blocks the main thread. That does not include things like IO that occur outside of the JavaScript interpreted code, so fetching data from an API for instance will not block the main thread. But execution of any code you write - variable assignments, functions, loops, expression evaluation, all that - occurs on the same thread that renders the DOM, so long calculations will cause the UI to freeze (again, unless you hand the computations off to a web worker).
So the question of blocking is unrelated to the call stack, which is the way the JS interpreter keeps track of what function is calling what function, so data is returned to the right caller. As for there being a "model" for assignments etc, the question is a bit unclear, but there is something called the "execution context". The execution context is closely related to the call stack in that a new execution context is created whenever a function is called on the stack. The execution context contains all the data the interpreter needs to execute the code within the current function (or top level code, if that's where we are).
You can read more about the details here: https://levelup.gitconnected.com/learn-javascript-fundamentals-scope-context-execution-context-9fe8673b3164
If you can specifically reference a point in the video, or what it describes, I can be more specific. But I'm not watching a 26 minute video to answer this! But I'll try.
A call stack is the way a javascript interpreter keeps track of where it is. For example, you call function a, a is pushed to the stack and run. If a calls b, then b is pushed to the stack. If b calls c then d, c is pushed to the stack, it completes, it's popped off, and b resumes where it was. Then it calls d and d runs and and returns and is popped off the stack. Then b finished, it's popped off the stack, and a continues where it was.
The stuff you mentioned, if statements, loops, assigning variables (and their scope) all happen inside functions (but it's more complex, scopes close over values and so forth). So yes, they do cause blocking, but they don't have their own spot on the call stack. You can think of them as the content of what runs on an item in the call stack.
https://developer.mozilla.org/en-US/docs/Glossary/Call_stack

Is it possible for Global Execution Context to pop off the execution stack?

When JS code starts to run, the Global Execution Context is created and sits at the bottom of the execution stack as to "accomodate" Global Variable Object and'this'.
If it is the case for the execution stack to get empty after the whole JS code is run and there is no Global Execution Context, how are we still able to access the global variables (for example, I am running an html file with its JS code and after its completion, I am still able to see the values of global variables through Chrome console...) or how this still points to global object (there shouldn't be any 'this' if there wasn't any Execution Context!)?
The only explanation I may give to myself is that Global Execution Context never leaves the execution stack; it is always there till I decide to close the browser window. Am I right or not?
Moreover, in case of asynchronous callbacks, when an event gets out of the event queue and gets into JS engine as to be run, what exactly happens in the execution stack? Is the callback's execution context sitting at the bottom of this stack or the global execution context is still there?
There is a similar subject Is the initial global execution context ever popped off the call stack in JavaScript?; however, it does not answer my questions.
Thank you
The execution stack gets empty when the whole code is run.
how are we still able to access the global variables?
Even when no code is executed the global lexical environment with the global object still exists. When you feed some code into chrome console, the code is being evaluated, a new global execution context is being created and initialized with its lexical and variable environments set to the global environment and this bound to the global object. Then your code is executed within this context and the execution stack gets empty again.
how this still points to global object?
Every time a new global execution context is initialized with the global code, this gets bound to the global object.
in case of asynchronous callbacks, when an event gets out of the event queue and gets into JS engine as to be 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 ECMAScript spec:
When the stack is empty, a message is taken out of the queue and processed. The processing consists of calling the associated function (and thus creating an initial stack frame). The 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 "global execution context".
Is the callback's execution context sitting at the bottom of this stack or the global execution context is still there?
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 running execution context and the execution context stack is empty, the ECMAScript implementation removes the first PendingJob from a Job Queue and uses the information contained in it to create an execution context and starts execution of the associated Job abstract operation. ECMAScript 6.0 spec

Is the initial global execution context ever popped off the call stack in JavaScript?

Is the "initial global execution context" ever popped off the call stack in JavaScript? I am talking about the execution context that is at the bottom of the stack at all times.
If so, I presume this means it is pushed onto the stack first before a callback is picked up off the Job Queue?
Alternatively, is it the [[Scope]].outer chain that provides access to the global environment whenever a callback is pushed onto the stack?
Is the "initial global execution context" ever popped off the call stack in JavaScript? I am talking about the execution context that is at the bottom of the stack at all times.
Yes, it is. An empty execution context stack is the requirement for any jobs to run.
However, there is no such thing like an "initial global execution context", and since the stack can be empty there is no single context that is at the bottom of the stack all the time.
"Global execution contexts" are created in ScriptEvaluations. Every script does have its own scriptCxt, yet all of them in a shared realm carry the same global environment records. These scriptCtxs are not at the bottom at the stack, though.
An "initial execution context" that sits at the bottom of the stack is created in the ECMAScript Initialisation process. It is pretty meaningless, for it does not hold anything but the new realm and only serves as the context for the initialisation of the realm and global object, but it is also used to start off the job queues.
If so, I presume this means it is pushed onto the stack first before a callback is picked up off the Job Queue?
Yes indeed. We can see this from the instructions for the NextJob algorithm steps. These are performed at the end of the ECMAScript initialisation and end of every job, and basically read as follows:
Suspend the current execution context and pop it from the stack so that the stack is empty.
Get the next job from any queue. If there are no more, proceed as you want (i.e. typically shuts down the process).
Create a new, empty (except for the realm of the job) context newContext and put it at the bottom of the stack
Execute the selected job in this context (which starts over NextJob in the end)
These contexts serve as the base for every job, containing all execution that ever happens. In PromiseJobs, they are used rather directly, while in module- and script evaluation jobs other contexts will be pushed on the stack that serve to hold the respective environment records with whom the code should be executed.
Alternatively, is it the [[Scope]].outer chain that provides access to the global environment whenever a callback is pushed onto the stack?
Yes indeed. The scope chain (that is not to be confused with the execution context stack) does provide access from everywhere to the global environment, which sits at the end of every scope chain.

Categories