GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||
GeezerVison is showing developer.mozilla.org — JavaScript execution model - JavaScript | MDN JavaScript execution modelThis page introduces the basic infrastructure of the JavaScript runtime environment. The model is largely theoretical and abstract, without any platform-specific or implementation-specific details. Modern JavaScript engines heavily optimize the described semantics. This page is a reference. It assumes you are already familiar with the execution model of other programming languages, such as C and Java. It makes heavy references to existing concepts in operating systems and programming languages. The engine and the hostJavaScript execution requires the cooperation of two pieces of software: the JavaScript engine and the host environment. The JavaScript engine implements the ECMAScript (JavaScript) language, providing the core functionality. It takes source code, parses it, and executes it. However, in order to interact with the outside world, such as to produce any meaningful output, to interface with external resources, or to implement security- or performance-related mechanisms, we need additional environment-specific mechanisms provided by the host environment. For example, the HTML DOM is the host environment when JavaScript is executed in a web browser. Node.js is another host environment that allows JavaScript to be run on the server side. While we focus primarily on the mechanisms defined in ECMAScript in this reference, we will occasionally talk about mechanisms defined in the HTML spec, which is often mimicked by other host environments like Node.js or Deno. This way, we can give a coherent picture of the JavaScript execution model as used on the web and beyond. Agent execution modelIn the JavaScript specification, each autonomous executor of JavaScript is called an agent, which maintains its facilities for code execution:
These are three distinct data structures that keep track of different data. We will introduce the queue and the stack in more detail in the following sections. To read more about how heap memory is allocated and freed, see memory management. Each agent is analogous to a thread (note that the underlying implementation may or may not be an actual operating system thread). Each agent can own multiple realms (which 1-to-1 correlate with global objects) that can synchronously access each other, and thus needs to run in a single execution thread. An agent also has a single memory model, indicating whether it's little-endian, whether it can be synchronously blocked, whether atomic operations are lock-free, etc. An agent on the web can be one of the following:
In other words, each worker creates its own agent, while one or more windows may be within the same agent—usually a main document and its similar-origin iframes. In Node.js, a similar concept called worker threads is available. The diagram below illustrates the execution model of agents: RealmsEach agent owns one or more realms. Each piece of JavaScript code is associated with a realm when it's loaded, which remains the same even when called from another realm. A realm consists of the following information:
On the web, the realm and the global object are 1-to-1 corresponded. The global object is either a Realms are usually mentioned when talking about the identities of global objects. For example, we need methods such as Stack and execution contextsWe first consider synchronous code execution. Each job enters by calling its associated callback. Code inside this callback may create variables, call functions, or exit. Each function needs to keep track of its own variable environments and where to return to. To handle this, the agent needs a stack to keep track of the execution contexts. An execution context, also known generally as a stack frame, is the smallest unit of execution. It tracks the following information:
Imagine a program consisting of a single job defined by the following code: js
Generators and reentryWhen a frame is popped, it's not necessarily gone forever, because sometimes we need to come back to it. For example, consider a generator function: js In this case, calling Tail callsOne mechanism defined in the specification is proper tail call (PTC). A function call is a tail call if the caller does nothing after the call except return the value: js In this case, the call to js In reality, discarding the current frame causes debugging problems, because if ClosuresAnother interesting phenomenon related to variable scoping and function calls is closures. Whenever a function is created, it also memorizes internally the variable bindings of the current running execution context. Then, these variable bindings can outlive the execution context. js Job queue and event loopAn agent is a thread, which means the interpreter can only process one statement at a time. When the code is all synchronous, this is fine because we can always make progress. But if the code needs to perform asynchronous action, then we cannot progress unless that action is completed. However, it would be detrimental to user experience if that halts the whole program—the nature of JavaScript as a web scripting language requires it to be never blocking. Therefore, the code that handles the completion of that asynchronous action is defined as a callback. This callback defines a job, which gets placed into a job queue—or, in HTML terminology, an event loop—once the action is completed. Every time, the agent pulls a job from the queue and executes it. When the job is executed, it may create more jobs, which are added to the end of the queue. Jobs can also be added via the completion of asynchronous platform mechanisms, such as timers, I/O, and events. A job is considered completed when the stack is empty; then, the next job is pulled from the queue. Jobs might not be pulled with uniform priority—for example, HTML event loops split jobs into two categories: tasks and microtasks. Microtasks have higher priority and the microtask queue is drained first before the task queue is pulled. For more information, check the HTML microtask guide. If the job queue is empty, the agent waits for more jobs to be added. "Run-to-completion"Each job is processed completely before any other job is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be preempted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread. For example, consider this example: js In this example, we create an already-resolved promise, which means any callback attached to it will be immediately scheduled as a job. The two callbacks seem to cause a race condition, but actually, the output is fully predictable: A downside of this model is that if a job takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make job processing short and, if possible, cut down one job into several jobs. Never blockingAnother important guarantee offered by the event loop model is that JavaScript execution is never blocking. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an IndexedDB query to return or a Of course, the guarantee of "never-blocking" requires the platform API to be inherently asynchronous, but some legacy exceptions exist like Agent clusters and memory sharingMultiple agents can communicate via memory sharing, forming an agent cluster. Agents are within the same cluster if and only if they can share memory. Agents not in the same cluster can still exchange information with other protocols, such as When creating an agent (such as by spawning a worker), there are some criteria for whether it's in the same cluster as the current agent, or a new cluster is created. For example, the following pairs of global objects are each within the same agent cluster, and thus can share memory with each other:
The following pairs of global objects are not within the same agent cluster, and thus cannot share memory:
For the exact algorithm, check the HTML specification. Cross-agent communication and memory modelAs aforementioned, agents communicate via memory sharing. On the web, memory is shared via the There are two ways to access shared memory: via normal memory access (which is not atomic) and via atomic memory access. The latter is sequentially consistent (which means there is a strict total ordering of events agreed upon by all agents in the cluster), while the former is unordered (which means no ordering exists); JavaScript does not provide operations with other ordering guarantees. The spec provides the following guidelines for programmers working with shared memory:
Concurrency and ensuring forward progressWhen multiple agents cooperate, the never-blocking guarantee does not always hold. An agent can become blocked, or paused, while waiting for another agent to perform some action. This is different from waiting on a promise in the same agent, because it halts the entire agent and does not allow any other code to run in the meantime—in other words, it cannot make forward progress. To prevent deadlocks, there are some strong restrictions on when and which agents can become blocked.
The agent cluster ensures some level of integrity over the activeness of its agents, in the case of external pauses or terminations:
Specifications
See also
| ||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |