ECMAScript The Core - noelno/dovelei GitHub Wiki

ECMAScript : langage orienté-objet avec une organisation basée sur le prototype.

Objet

Objet : collection de propriétés avec un prototype qui est soit un objet, soit null.

Chaque objet a une propriété interne [Prototype](/noelno/dovelei/wiki/Prototype) qui contient une référence vers son prototype. Certains navigateurs implémentent la propriété proto pour donner accès au prototype.

Prototype

Prototype : objet reçu par un autre objet à la création de ce dernier et par lequel peut être mis en place un système d'héritage par délégation.

L'objet reçu peut être défini explicitement via __proto__ ou Object.create, sinon par défaut il s'agira du même prototype que leur parent (typiquement Function.prototype)

N'importe quel objet peut être le prototype de n'importe quel autre, et plusieurs objets peuvent avoir le même prototype.

Lorsque l'on tente d'accéder à une propriété inexistante dans un objet, en ECMAScript l'implémentation regarde dans le prototype et remonte toute la chaîne jusqu'à ce qu'une propriété du même nom soit trouvée, ou à defaut retourne undefined.

Pour créer un objet vide, sans prototype : Object.create(null);

Le prototype d'un objet peut être modifié à la volée : Object.setPrototypeOf(monInstance,UnObjetDontJeSouhaiteRecupererLePrototype);

Classe

Lorsque plusieurs objets partagent le même état initial et les mêmes comportement, on dit qu'ils appartiennent à la même classification.
Une classe est une configuration définissant l'état initial et le comportement de ses instances.

Le moyen le plus simple de mettre en place l'héritage en JS est de créer un objet qui servira de prototype à plusieurs autres, et de l'affecter à la propriété proto des héritiers.

// Generic prototype for all letters.
let letter = {
  getNumber() {
    return this.number;
  }
};
 
let a = {number: 1, __proto__: letter};
let b = {number: 2, __proto__: letter};
// ...
let z = {number: 26, __proto__: letter};
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

Le sucre syntaxique Class, disponible depuis ES6 (donc incompatible IE) permet d'implémenter ce système avec une syntaxe plus légère et plus esthétique. C'est d'ailleurs la définition d'un sucre syntaxique : offrir une alternative plus esthétique à une construction.

class Letter {
  constructor(number) {
    this.number = number;
  }
 
  getNumber() {
    return this.number;
  }
}
 
let a = new Letter(1);
let b = new Letter(2);
// ...
let z = new Letter(26);
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

Une classe contient une fonction constructrice et un prototype. new Objet construit l'objet en lui attribuant des propriétés de base, et lui attribue aussi un prototype. Avant l'existence de l'abstraction Class, on faisait comme ceci :

function Letter(number) {
  this.number = number;
}
 
Letter.prototype.getNumber = function() {
  return this.number;
};
 
let a = new Letter(1);
let b = new Letter(2);
// ...
let z = new Letter(26);
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

À noter que la propriété Letter.prototype n'a rien à voir avec Letter.__proto__. Le premier est le prototype des instances de Letter (a, b, z…), alors que Letter.__proto__ est le prototype de Letter.

Contexte d'exécution

Un contexte d'exécution est une délimitation conceptuelle, une zone où le code est exécuté. À chaque appel de fonction, un nouveau contexte d'exécution est crée et s'empile sur le contexte précédent. À chaque retour de fonction, le contexte d'exécution de la fonction est dépilé et détruit. Le premier contexte est le contexte global. Le contexte appelant un autre contexte est appelé « contexte appelant ».

Les générateurs ES6 sont des fonctions spéciales qui permettent de définir un comportement dans une boucle. Les générateurs ont pour spécificité de ne pas rempiler un nouveau contexte quand ils sont appelés, mais de réutiliser toujours le même, ce qui leur permet de mémoriser leur état pendant toute l'exécution du programme.

function *gen() {
  yield 1;
  return 2;
}
 
let g = gen();
 
console.log(
  g.next().value, // 1
  g.next().value, // 2
);

Environnement

Chaque contexte d'exécution est associé à un environnement lexical (remplaçants des GO et VO en ES3).
L'environnement lexical permet de référencer les valeurs déclarées dans le contexte et leurs identifiants (les noms des variables) dans un tableau. Il référence aussi éventuellement un objet parent, ou null.

La résolution d'identifiant se passe à travers ces environnements : on remonte tous les environnements parents jusqu'à ce que l'on tombe vers l'identifiant recherché. Si l'identifiant n'est pas trouvé, une ReferenceError est levée.

Closure

Les fonctions ECMAScript sont de première classe, c'est-à-dire qu'elles peuvent être affectées / passées en paramètres / retournées par une autre fonction, comme des valeurs normales.

Une variable libre (par-rapport à une fonction) n'est ni un paramètre, ni une variable locale de la fonction. C'est une variable présente dans l'un des contextes parents à la fonction courante.

Le « funarg problem » : quand une fonction B est passée en paramètre d'une fonction A, elle ne va pas chercher les valeurs dans l'environnement lexical de A, mais dans l'environnement lexical de l'endroit où est déclarée la fonction : ici foo() est déclaré dans le contexte global, donc foo() va chercher x dans le contexte global.

let x = 10;
 
function foo() {
  console.log(x);
}
 
function bar(funArg) {
  let x = 20;
  funArg(); // 10, not 20!
}
 
// Pass `foo` as an argument to `bar`.
bar(foo);

En ECMAScript les portées des identifiants sont statiques : elles sont définies au moment de la création de la fonction. Exception faite de this. Regarder la syntaxe de la déclaration de la fonction suffit à savoir quelle sera la valeur.

Une closure est une fonction qui capture l'environnement là où elle est définie.

function createCounter() {
  let count = 0;
 
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; },
  };
}
 
let counter = createCounter();
 
console.log(
  counter.increment(), // 1
  counter.decrement(), // 0
  counter.increment(), // 1
);

This

this est un objet spécial qui est dynamiquement communiqué au contexte courant comme un paramètre implicite. Cet objet est utilisé pour faire référence aux propriétés communes à toutes les instances de classe :

class Point {
  constructor(x, y) {
    this._x = x;
    this._y = y;
  }
 
  getX() {
    return this._x;
  }
 
  getY() {
    return this._y;
  }
}
 
let p1 = new Point(1, 2);
let p2 = new Point(3, 4);
 
// Can access `getX`, and `getY` from
// both instances (they are passed as `this`).
 
console.log(
  p1.getX(), // 1
  p2.getX(), // 3
);

Il est aussi utilisé pour créer des interfaces :

// Generic Movable interface (mixin).
let Movable = {
 
  /**
   * This function is generic, and works with any
   * object, which provides `_x`, and `_y` properties,
   * regardless of the class of this object.
   */
  move(x, y) {
    this._x = x;
    this._y = y;
  },
};
 
let p1 = new Point(1, 2);
 
// Make `p1` movable.
Object.assign(p1, Movable);
 
// Can access `move` method.
p1.move(100, 200);
 
console.log(p1.getX()); // 100

this a une portée dynamique : sa valeur dépend de la forme de l'instruction d'appel.

function foo() {
  return this;
}
 
let bar = {
  foo,
 
  baz() {
    return this;
  },
};
 
// `foo`
console.log(
  foo(),       // global or undefined
 
  bar.foo(),   // bar
  (bar.foo)(), // bar
 
  (bar.foo = bar.foo)(), // global
);
 
// `bar.baz`
console.log(bar.baz()); // bar
 
let savedBaz = bar.baz;
console.log(savedBaz()); // global

Le this dans les fonctions fléchées renvoie au contexte appelant :

var x = 10;
 
let foo = {
  x: 20,
 
  // Dynamic `this`.
  bar() {
    return this.x;
  },
 
  // Lexical `this`.
  baz: () => this.x,
 
  qux() {
    // Lexical this within the invocation.
    let arrow = () => this.x;
 
    return arrow();
  },
};
 
console.log(
  foo.bar(), // 20, from `foo`
  foo.baz(), // 10, from global
  foo.qux(), // 20, from `foo` and arrow
);

Domaines et jobs

Tout code est associé à un domaine. Ce dernier fournit un environnement lexical au code. Il y a deux files de jobs : celle des scripts et modules, et celle des promesses / async. Lors de l'initialisation du programme, ce programme est placé dans la file des scripts. Il est ensuite associé à un domaine, un contexte d'exécution et poussé en haut de la pile du contexte d'exécution. Une fois que le programme a terminé son exécution et que la pile de contexte est vide, les jobs en attente dans leur file sont exécutés l'un après l'autre (boucle des événements)

Sources

http://dmitrysoshnikov.com/ecmascript/javascript-the-core-2nd-edition/ https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/iterateurs_et_generateurs