[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Execution_model [Back]  [Original]

Modle d'excution JavaScript - JavaScript | MDN

Cette page a t traduite partir de l'anglais par la communaut. Vous pouvez contribuer en rejoignant la communaut francophone sur MDN Web Docs.

View in English Always switch to English

Modle d'excution JavaScript

Cette page prsente l'infrastructure de base de l'environnement d'excution JavaScript. Le modle est principalement thorique et abstrait, sans aucun dtail spcifique une plateforme ou une implmentation. Les moteurs JavaScript modernes optimisent fortement la smantique dcrite ici.

Cette page est une rfrence. Elle suppose que vous connaissez dj le modle d'excution d'autres langages de programmation, comme C ou Java. Elle fait de nombreuses rfrences des concepts existants dans les systmes d'exploitation et les langages de programmation.

Dans cet article

Le moteur et l'hte

L'excution JavaScript ncessite la coopration de deux logiciels : le moteur JavaScript et l'environnement hte.

Le moteur JavaScript implmente le langage ECMAScript (JavaScript), fournissant les fonctionnalits de base. Il prend le code source, l'analyse et l'excute. Cependant, pour interagir avec le monde extrieur, produire une sortie utile, accder des ressources externes ou mettre en uvre des mcanismes lis la scurit ou aux performances, il faut des mcanismes supplmentaires fournis par l'environnement hte. Par exemple, le DOM HTML est l'environnement hte lorsque JavaScript s'excute dans un navigateur web. Node.js est un autre environnement hte qui permet d'excuter JavaScript ct serveur.

Bien que cette page se concentre principalement sur les mcanismes dfinis dans ECMAScript, elle aborde parfois des mcanismes dfinis dans la spcification HTML, souvent imits par d'autres environnements htes comme Node.js ou Deno. Cela permet de donner une vision cohrente du modle d'excution JavaScript tel qu'il est utilis sur le web et au-del.

Modle d'excution d'agent

Dans la spcification JavaScript, chaque excuteur autonome de JavaScript est appel un agent, qui maintient ses propres structures pour l'excution du code :

  • Tas (d'objets) : il s'agit simplement d'une grande zone de mmoire (principalement non structure). Elle se remplit au fur et mesure que des objets sont crs dans le programme. En cas de mmoire partage, chaque agent possde son propre tas avec sa propre version d'un objet SharedArrayBuffer, mais la mmoire sous-jacente reprsente par le buffer est partage.
  • Queue (de tches) : appele boucle d'vnement en HTML (et couramment), elle permet la programmation asynchrone en JavaScript tout en restant monothread. On parle de queue car elle fonctionne gnralement en premier entr, premier sorti : les tches les plus anciennes sont excutes avant les plus rcentes.
  • Pile (de contextes d'excution) : c'est la pile d'appels qui permet de transfrer le flux de contrle en entrant et sortant des contextes d'excution comme les fonctions. On parle de pile car elle fonctionne en dernier entr, premier sorti. Chaque tche commence en empilant un nouveau cadre sur la pile (vide), et se termine en vidant la pile.

Ce sont trois structures de donnes distinctes qui grent des informations diffrentes. Nous prsenterons la queue et la pile plus en dtail dans les sections suivantes. Pour en savoir plus sur l'allocation et la libration de la mmoire du tas, voir gestion de la mmoire.

Chaque agent est analogue un thread (ou fil d'excution) (l'implmentation sous-jacente peut ou non tre un vrai thread systme). Chaque agent peut possder plusieurs realms (qui correspondent 1--1 des objets globaux) pouvant s'accder mutuellement de faon synchrone, et doit donc s'excuter dans un seul thread. Un agent possde aussi un modle mmoire unique, indiquant s'il est petit-boutiste (little-endian en anglais), s'il peut tre bloqu de faon synchrone, si les oprations atomiques sont sans verrou, etc.

Un agent sur le web peut tre l'un des suivants :

En d'autres termes, chaque worker cre son propre agent, tandis qu'une ou plusieurs fentres peuvent appartenir au mme agent gnralement un document principal et ses iframes de mme origine. Dans Node.js, un concept similaire appel worker threads (angl.) existe.

Le schma ci-dessous illustre le modle d'excution des agents :

Un schma compos de deux agents : une page HTML et un worker. Chacun possde sa propre pile contenant des contextes d'excution, un tas contenant des objets et une queue contenant des tches. [Un schma compos de deux agents : une page HTML et un worker. Chacun possde sa propre pile contenant des contextes d'excution, un tas contenant des objets et une queue contenant des tches.]

Domaine d'excution (Realm)

Chaque agent possde un ou plusieurs realms. Chaque morceau de code JavaScript est associ un realm lors de son chargement, ce qui reste vrai mme s'il est appel depuis un autre realm. Un realm contient les informations suivantes :

  • Une liste d'objets intrinsques comme Array, Array.prototype, etc.
  • Les variables globales dclares, la valeur de globalThis, et l'objet global
  • Un cache des tableaux de templates littraux, car l'valuation d'une mme expression de template littral balis fait toujours recevoir au tag le mme objet tableau

Sur le web, le realm et l'objet global correspondent 1--1. L'objet global est soit un Window, soit un WorkerGlobalScope, soit un WorkletGlobalScope. Par exemple, chaque iframe s'excute dans un realm diffrent, mme s'il peut tre dans le mme agent que la fentre parente.

Les realms sont gnralement mentionns lorsqu'on parle de l'identit des objets globaux. Par exemple, on a besoin de mthodes comme Array.isArray() ou Error.isError(), car un tableau construit dans un autre realm aura un prototype diffrent de Array.prototype dans le realm courant, donc instanceof Array retournera tort false.

Pile et contextes d'excution

Commenons par l'excution synchrone du code. Chaque tche commence en appelant son callback associ. Le code l'intrieur de ce callback peut crer des variables, appeler des fonctions ou sortir. Chaque fonction doit garder la trace de ses propres environnements de variables et de l'endroit o retourner. Pour cela, l'agent a besoin d'une pile pour suivre les contextes d'excution. Un contexte d'excution (ou stack frame) est la plus petite unit d'excution. Il contient les informations suivantes :

  • L'tat d'valuation du code
  • Le module ou script, la fonction (si applicable), et le gnrateur actuellement excut qui contient ce code
  • Le domaine d'excution courant
  • Les liaisons, incluant :
    • Les variables dfinies avec var, let, const, function, class, etc.
    • Les identifiants privs comme #foo qui ne sont valides que dans le contexte courant
    • La rfrence this

Imaginons un programme constitu d'une seule tche dfinie par le code suivant :

js
function toto(b) {
  const a = 10;
  return a + b + 11;
}

function tata(x) {
  const y = 3;
  return toto(x * y);
}

const truc = tata(7); // assigne 42  truc
  1. Quand la tche commence, le premier cadre est cr, o les variables toto, tata et truc sont dfinies. On appelle tata avec l'argument 7.
  2. Un deuxime cadre est cr pour l'appel tata, contenant les liaisons pour le paramtre x et la variable locale y. On effectue d'abord la multiplication x * y, puis on appelle toto avec le rsultat.
  3. Un troisime cadre est cr pour l'appel toto, contenant les liaisons pour le paramtre b et la variable locale a. On effectue d'abord l'addition a + b + 11, puis on retourne le rsultat.
  4. Quand toto retourne, l'lment du haut de la pile est dpil, et l'expression d'appel toto(x * y) se rsout en la valeur de retour. On continue l'excution, qui consiste simplement retourner ce rsultat.
  5. Quand tata retourne, l'lment du haut de la pile est dpil, et l'expression d'appel tata(7) se rsout en la valeur de retour. Cela initialise truc avec la valeur de retour.
  6. On atteint la fin du code source de la tche, donc le cadre d'entre est dpil. La pile est vide, la tche est termine.

Gnrateurs et rentres

Quand un cadre est dpil, il n'est pas forcment perdu pour toujours, car il arrive qu'on doive y revenir. Par exemple, considrons une fonction gnratrice :

js
function* gen() {
  console.log(1);
  yield;
  console.log(2);
}

const g = gen();
g.next(); // affiche 1
g.next(); // affiche 2

Dans ce cas, appeler gen() cre d'abord un contexte d'excution qui est suspendu aucun code l'intrieur de gen n'est encore excut. Le gnrateur g sauvegarde ce contexte d'excution en interne. Le contexte d'excution courant reste celui du point d'entre. Quand on appelle g.next(), le contexte d'excution de gen est empil, et le code l'intrieur de gen s'excute jusqu' l'expression yield. Ensuite, le contexte d'excution du gnrateur est suspendu et retir de la pile, ce qui rend la main au point d'entre. Quand on appelle g.next() nouveau, le contexte d'excution du gnrateur est r-empil, et le code l'intrieur de gen reprend l o il s'tait arrt.

Appels en queue

Un mcanisme dfini dans la spcification est l'appel en queue propre (PTC). Un appel de fonction est un appel en queue si l'appelant ne fait rien aprs l'appel sauf retourner la valeur :

js
function f() {
  return g();
}

Dans ce cas, l'appel g est un appel en queue. Si un appel de fonction est en position de queue, le moteur doit supprimer le contexte d'excution courant et le remplacer par celui de l'appel en queue, au lieu d'empiler un nouveau cadre pour l'appel g(). Cela signifie que la rcursion terminale n'est pas soumise aux limites de taille de pile :

js
function factoriel(n, acc = 1) {
  if (n <= 1) return acc;
  return factoriel(n - 1, n * acc);
}

En pratique, supprimer le cadre courant pose des problmes de dbogage, car si g() lve une erreur, f n'est plus sur la pile et n'apparat pas dans la trace. Actuellement, seul Safari (JavaScriptCore) implmente PTC, et ils ont invent une infrastructure spcifique (angl.) pour rsoudre ce problme de dbogabilit.

Fermetures (closures)

Un autre phnomne intressant li la porte des variables et aux appels de fonction est celui des fermetures. Lorsqu'une fonction est cre, elle mmorise aussi en interne les liaisons de variables du contexte d'excution courant. Ces liaisons peuvent alors survivre au contexte d'excution.

js
let f;
{
  let x = 10;
  f = () => x;
}
console.log(f()); // affiche 10

Queue de tches et boucle d'vnement

Un agent est un thread, ce qui signifie que l'interprteur ne peut traiter qu'une instruction la fois. Quand le code est entirement synchrone, cela fonctionne car on peut toujours avancer. Mais si le code doit effectuer une action asynchrone, on ne peut pas progresser tant que cette action n'est pas termine. Cependant, cela nuirait l'exprience utilisateur si cela bloquait tout le programme la nature de JavaScript comme langage de script web exige qu'il soit non bloquant. Ainsi, le code qui gre la fin d'une action asynchrone est dfini comme un callback. Ce callback dfinit une tche, qui est place dans une queue de tches ou, en HTML, une boucle d'vnement une fois l'action termine.

chaque fois, l'agent prend une tche dans la queue et l'excute. Lorsqu'une tche est excute, elle peut en crer d'autres, qui sont ajoutes la fin de la queue. Les tches peuvent aussi tre ajoutes par la compltion de mcanismes asynchrones de la plateforme, comme les timers, les oprations d'entre/sortie ou les vnements. Une tche est considre comme termine quand la pile est vide ; la tche suivante est alors prise dans la queue. Les tches ne sont pas forcment traites avec la mme priorit par exemple, les boucles d'vnement HTML sparent les tches en deux catgories : tches et micro-tches. Les micro-tches ont une priorit plus leve et la queue de micro-tches est vide avant que la queue des tches ne soit traite. Pour plus d'informations, voir le guide HTML sur les micro-tches. Si la queue de tches est vide, l'agent attend que d'autres tches soient ajoutes.

 Run-to-completion 

Chaque tche est traite compltement avant toute autre tche. Cela offre des proprits intressantes pour raisonner sur votre programme, notamment le fait que lorsqu'une fonction s'excute, elle ne peut pas tre interrompue et s'excutera entirement avant tout autre code (et pourra modifier les donnes manipules). Cela diffre de C, par exemple, o si une fonction s'excute dans un thread, elle peut tre arrte tout moment par le systme d'excution pour excuter un autre code dans un autre thread.

Par exemple :

js
const promise = Promise.resolve();
let i = 0;
promise.then(() => {
  i += 1;
  console.log(i);
});
promise.then(() => {
  i += 1;
  console.log(i);
});

Dans cet exemple, on cre une promesse dj rsolue, ce qui signifie que tout retour d'appel attach sera immdiatement planifi comme tche. Les deux retours d'appels semblent provoquer une condition de course, mais en ralit, le rsultat est totalement prvisible : 1 et 2 seront affichs dans l'ordre. En effet, chaque tche s'excute jusqu'au bout avant que la suivante ne soit lance, donc l'ordre global est toujours i += 1; console.log(i); i += 1; console.log(i); et jamais i += 1; i += 1; console.log(i); console.log(i);.

Un inconvnient de ce modle est que si une tche prend trop de temps s'excuter, l'application web ne peut plus traiter les interactions utilisateur comme les clics ou le dfilement. Le navigateur attnue cela avec le message  un script met trop de temps s'excuter . Une bonne pratique consiste garder le traitement des tches court et, si possible, dcouper une tche problmatique en plusieurs tches.

Jamais bloquant

Une autre garantie importante offerte par le modle de boucle d'vnement est que l'excution JavaScript n'est jamais bloquante. La gestion d'oprations d'entre/sortie se fait gnralement via des vnements et des retours d'appels, donc quand l'application attend le rsultat d'une requte IndexedDB ou d'un appel fetch(), elle peut toujours traiter d'autres lments comme les saisies utilisateur. Le code qui s'excute aprs la fin d'une action asynchrone est toujours fourni sous forme de callback (par exemple, le gestionnaire then(), le callback de setTimeout(), ou le gestionnaire d'vnement), qui dfinit une tche ajouter la queue une fois l'action termine.

Bien sr, la garantie de  jamais bloquant  suppose que l'API de la plateforme soit asynchrone, mais il existe quelques exceptions historiques comme alert() ou les XHR synchrones. Il est recommand de les viter pour garantir la ractivit de l'application.

Groupes d'agents et partage de mmoire

Plusieurs agents peuvent communiquer via le partage de mmoire, formant un groupe d'agents. Les agents sont dans le mme groupe si et seulement s'ils peuvent partager la mmoire. Il n'existe aucun mcanisme intgr pour que deux groupes d'agents changent des informations, ils peuvent donc tre considrs comme des modles d'excution compltement isols.

Lors de la cration d'un agent (par exemple en lanant un worker), certains critres dterminent s'il appartient au mme groupe que l'agent courant ou si un nouveau groupe est cr. Par exemple, les paires d'objets globaux suivantes sont chacune dans le mme groupe d'agents et peuvent donc partager la mmoire :

  • Un objet Window et un worker ddi qu'il a cr.
  • Un worker (de n'importe quel type) et un worker ddi qu'il a cr.
  • Un objet Window A et l'objet Window d'un lment iframe de mme origine cr par A.
  • Un objet Window et un objet Window de mme origine qui l'a ouvert.
  • Un objet Window et un worklet qu'il a cr.

Les paires d'objets globaux suivantes ne sont pas dans le mme groupe d'agents et ne peuvent donc pas partager la mmoire :

  • Un objet Window et un worker partag qu'il a cr.
  • Un worker (de n'importe quel type) et un worker partag qu'il a cr.
  • Un objet Window et un service worker qu'il a cr.
  • Un objet Window A et l'objet Window d'un lment iframe cr par A qui ne peut pas tre de mme origine que A.
  • Deux objets Window sans relation d'ouverture ou d'ascendance. Cela reste vrai mme si les deux objets Window sont de mme origine.

Pour l'algorithme exact, voir la spcification HTML (angl.).

Communication inter-agents et modle mmoire

Comme mentionn plus haut, les agents communiquent via le partage de mmoire. Sur le web, la mmoire est partage via la mthode postMessage(). Le guide Utiliser les web workers donne un aperu de ce mcanisme. En gnral, les donnes sont transmises uniquement par valeur (via le clonage structur), ce qui vite toute complication de concurrence. Pour partager la mmoire, il faut transmettre un objet SharedArrayBuffer, qui peut tre accd simultanment par plusieurs agents. Une fois que deux agents partagent l'accs la mme mmoire via un SharedArrayBuffer, ils peuvent synchroniser leurs excutions via l'objet Atomics.

Il existe deux faons d'accder la mmoire partage : via un accs mmoire normal (non atomique) et via un accs mmoire atomique. Ce dernier est squentiellement cohrent (angl.) (c'est--dire qu'il existe un ordre total strict des vnements accept par tous les agents du groupe), tandis que le premier n'est pas ordonn (aucun ordre n'est garanti) ; JavaScript ne fournit pas d'oprations avec d'autres garanties d'ordre.

La spcification donne les recommandations suivantes pour les dveloppeureuses travaillant avec la mmoire partage :

Il est recommand d'crire des programmes sans conditions de concurrence (data race free), c'est--dire de faire en sorte qu'il soit impossible d'avoir des oprations non atomiques concurrentes sur la mme zone mmoire. Les programmes sans conditions de concurrence ont une smantique d'entrelacement o chaque tape de l'valuation de chaque agent s'entrelace avec les autres. Pour ces programmes, il n'est pas ncessaire de comprendre les dtails du modle mmoire. Ces dtails n'apportent gnralement pas d'intuition utile pour mieux crire de l'ECMAScript.

Plus gnralement, mme si un programme n'est pas data race free, il peut avoir un comportement prvisible, tant que les oprations atomiques ne sont pas impliques dans des courses et que les oprations en course ont toutes la mme taille d'accs. Le moyen le plus simple d'viter que les atomiques soient impliques dans des courses est de s'assurer que diffrentes cellules mmoire sont utilises pour les oprations atomiques et non atomiques, et que des accs atomiques de tailles diffrentes ne sont pas utiliss sur les mmes cellules en mme temps. En pratique, le programme doit traiter la mmoire partage comme fortement type autant que possible. On ne peut toujours pas dpendre de l'ordre et du timing des accs non atomiques en course, mais si la mmoire est traite comme fortement type, les accs concurrents ne  dchirent  pas (les bits de leurs valeurs ne seront pas mlangs).

Concurrence et progression garantie

Lorsque plusieurs agents cooprent, la garantie jamais bloquant ne s'applique pas toujours. Un agent peut tre bloqu ou mis en pause en attendant qu'un autre agent effectue une action. Cela diffre de l'attente d'une promesse dans le mme agent, car cela bloque tout l'agent et n'autorise aucun autre code s'excuter entre-temps autrement dit, il ne peut pas progresser.

Pour viter les interblocages (deadlocks), il existe des restrictions strictes sur les moments et les agents qui peuvent tre bloqus.

  • Tout agent non bloqu avec un thread d'excution ddi finit par progresser.
  • Dans un ensemble d'agents partageant un thread d'excution, un agent finit par progresser.
  • Un agent ne bloque pas un autre agent sauf via des API explicites qui permettent le blocage.
  • Seuls certains agents peuvent tre bloqus. Sur le web, cela inclut les workers ddis et partags, mais pas les fentres de mme origine ni les service workers.

Le groupe d'agents garantit un certain niveau d'intgrit sur l'activit de ses agents, en cas de pause ou de terminaison externe :

  • Un agent peut tre mis en pause ou repris sans qu'il le sache ou y consente. Par exemple, naviguer hors d'une fentre peut suspendre l'excution du code mais prserver son tat. Cependant, un groupe d'agents ne peut pas tre partiellement dsactiv, pour viter qu'un agent soit affam parce qu'un autre a t dsactiv. Par exemple, les shared workers ne sont jamais dans le mme groupe que la fentre cratrice ou d'autres workers ddis. En effet, la dure de vie d'un shared worker est indpendante des documents : si un document est dsactiv alors que son worker ddi dtient un verrou, le shared worker ne pourra pas acqurir le verrou tant que le worker ddi n'est pas ractiv, voire jamais. Pendant ce temps, d'autres workers essayant d'accder au shared worker depuis d'autres fentres seront affams.
  • De mme, un agent peut tre termin par des facteurs externes au groupe. Par exemple, un systme d'exploitation ou une utilisateurice qui tue un processus navigateur, ou le navigateur qui force la terminaison d'un agent parce qu'il utilise trop de ressources. Dans ce cas, tous les agents du groupe sont termins. (La spcification autorise aussi une seconde stratgie, une API permettant au moins un membre restant du groupe d'identifier la terminaison et l'agent termin, mais cela n'est pas implment sur le web.)

Spcifications

Spcification
ECMAScript 2027 LanguageSpecification
ECMAScript 2027 LanguageSpecification
HTML

Voir aussi


Web Proxy Viewer  |  New URL  |  Original Page