iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
JavaScript · Débutant

JavaScript

Apprendre JavaScript de zéro jusqu'au niveau expert : syntaxe moderne, DOM, asynchrone et event loop, modules, prototypes, métaprogrammation, performance et mémoire.

JavaScript ES2025Node.jsDOMVitestVite
01Introduction et environnement02Variables, types et coercition03Conditions et boucles04Fonctions, portée et closures05Objets, tableaux, Map et Set06Le DOM et les événements07Asynchrone : callbacks, promesses, async/await et event loop08Modules, npm, outillage et tests09Prototypes, classes et this10Itérateurs, générateurs, Symbol, Proxy et métaprogrammation11Niveau expert : moteur, performance, mémoire et architecture
Chapitre 10·55 min

Itérateurs, générateurs, Symbol, Proxy et métaprogrammation

Ce chapitre couvre les mécanismes qui permettent à un objet de s'intégrer au langage lui-même : être parcouru par for...of, réagir à chaque lecture de propriété, se comporter comme une primitive. Vue 3 construit toute sa réactivité sur Proxy. Redux-Saga repose sur les générateurs. Les comprendre, c'est pouvoir lire ces bibliothèques.

Symbol

Un Symbol est une valeur primitive unique, utilisable comme clé de propriété sans risque de collision.

const ID = Symbol('id')
const user = { nom: 'Ada', [ID]: 42 }
 
user[ID]                 // 42
Object.keys(user)        // ['nom'] — les symboles sont ignorés
JSON.stringify(user)     // '{"nom":"Ada"}'
Symbol('id') === Symbol('id')   // false
Symbol.for('app.id') === Symbol.for('app.id')   // true — registre global

Les symboles bien connus permettent de brancher un objet sur les mécanismes du langage :

SymboleUtilisé par
Symbol.iteratorfor...of, spread, déstructuration
Symbol.asyncIteratorfor await...of
Symbol.toPrimitiveconversions +obj, `${obj}`
Symbol.toStringTagObject.prototype.toString
Symbol.hasInstanceinstanceof
class Monnaie {
  constructor(centimes, devise) {
    this.centimes = centimes
    this.devise = devise
  }
  [Symbol.toPrimitive](hint) {
    return hint === 'number' ? this.centimes / 100 : `${(this.centimes / 100).toFixed(2)} ${this.devise}`
  }
}
 
const prix = new Monnaie(1999, 'EUR')
String(prix)   // '19.99 EUR'
+prix       // 19.99

Le protocole d'itération

Un objet est itérable s'il possède une méthode [Symbol.iterator] qui renvoie un itérateur : un objet avec une méthode next() renvoyant { value, done }.

class Intervalle {
  constructor(debut, fin, pas = 1) {
    Object.assign(this, { debut, fin, pas })
  }
 
  [Symbol.iterator]() {
    let courant = this.debut
    const { fin, pas } = this
    return {
      next: () =>
        courant <= fin
          ? { value: (courant += pas) - pas, done: false }
          : { value: undefined, done: true },
    }
  }
}
 
for (const n of new Intervalle(1, 10, 3)) console.log(n)   // 1, 4, 7, 10
const liste = [...new Intervalle(0, 4)]                      // [0, 1, 2, 3, 4]

Générateurs

Une fonction function* renvoie un itérateur. yield suspend l'exécution et rend une valeur ; l'appel suivant à next() reprend juste après.

class Intervalle2 {
  constructor(debut, fin, pas = 1) {
    Object.assign(this, { debut, fin, pas })
  }
  *[Symbol.iterator]() {
    for (let n = this.debut; n <= this.fin; n += this.pas) yield n
  }
}

Beaucoup plus court. Les générateurs sont paresseux : ils ne calculent que ce qu'on consomme.

function* naturels() {
  let n = 0
  while (true) yield n++
}
 
function* filtrer(iter, pred) {
  for (const x of iter) if (pred(x)) yield x
}
 
function* prendre(iter, n) {
  for (const x of iter) {
    if (n-- <= 0) return
    yield x
  }
}
 
[...prendre(filtrer(naturels(), n => n % 7 === 0), 5)]   // [0, 7, 14, 21, 28]

Une suite infinie, filtrée et tronquée, sans jamais construire de tableau intermédiaire.

Iterator helpers (ES2025)

Les itérateurs ont désormais des méthodes natives :

naturels()
  .filter(n => n % 7 === 0)
  .map(n => n * n)
  .take(5)
  .toArray()    // [0, 49, 196, 441, 784]

Communication bidirectionnelle

next(valeur) injecte une valeur comme résultat du yield en cours :

function* dialogue() {
  const nom = yield 'Comment tu t\'appelles ?'
  const age = yield `Enchanté ${nom}, quel âge ?`
  return `${nom}, ${age} ans`
}
 
const g = dialogue()
g.next().value          // 'Comment tu t'appelles ?'
g.next('Ada').value     // 'Enchanté Ada, quel âge ?'
g.next(36).value        // 'Ada, 36 ans'

C'est exactement le mécanisme qu'utilisaient les bibliothèques comme co pour simuler async/await avant qu'il existe.

Générateurs asynchrones

async function* paginer(url) {
  while (url) {
    const res = await fetch(url)
    const { items, next } = await res.json()
    yield* items
    url = next
  }
}
 
for await (const article of paginer('/api/articles?page=1')) {
  console.log(article.titre)
  if (article.id === cible) break    // les pages suivantes ne sont jamais chargées
}

Proxy

Un Proxy enveloppe un objet et intercepte les opérations fondamentales : lecture, écriture, suppression, in, appel de fonction…

const cible = { nom: 'Ada' }
const proxy = new Proxy(cible, {
  get(obj, prop, receiver) {
    console.log(`lecture de ${String(prop)}`)
    return Reflect.get(obj, prop, receiver)
  },
  set(obj, prop, valeur, receiver) {
    console.log(`écriture de ${String(prop)} = ${valeur}`)
    return Reflect.set(obj, prop, valeur, receiver)
  },
})
 
proxy.nom          // lecture de nom
proxy.age = 36     // écriture de age = 36

Reflect fournit l'implémentation par défaut de chaque opération. L'utiliser dans les pièges garantit un comportement correct avec les getters et l'héritage.

Validation

function schema(regles) {
  return new Proxy({}, {
    set(obj, prop, valeur) {
      const regle = regles[prop]
      if (!regle) throw new TypeError(`Propriété inconnue : ${prop}`)
      if (!regle(valeur)) throw new TypeError(`Valeur invalide pour ${prop} : ${valeur}`)
      obj[prop] = valeur
      return true
    },
  })
}
 
const user = schema({
  email: v => /^[^@\s]+@[^@\s]+$/.test(v),
  age: v => Number.isInteger(v) && v >= 0,
})
 
user.email = 'ada@example.com'
user.age = -1        // TypeError: Valeur invalide pour age : -1
user.role = 'admin'  // TypeError: Propriété inconnue : role

Réactivité à la Vue

Le cœur de la réactivité de Vue 3 tient en quelques lignes :

let effetActif = null
const dependances = new WeakMap()   // objet → Map(propriété → Set(effets))
 
function suivre(obj, prop) {
  if (!effetActif) return
  let parProp = dependances.get(obj)
  if (!parProp) dependances.set(obj, (parProp = new Map()))
  let effets = parProp.get(prop)
  if (!effets) parProp.set(prop, (effets = new Set()))
  effets.add(effetActif)
}
 
function declencher(obj, prop) {
  dependances.get(obj)?.get(prop)?.forEach(effet => effet())
}
 
function reactive(obj) {
  return new Proxy(obj, {
    get(o, p, r) {
      suivre(o, p)
      return Reflect.get(o, p, r)
    },
    set(o, p, v, r) {
      const ok = Reflect.set(o, p, v, r)
      declencher(o, p)
      return ok
    },
  })
}
 
function effect(fn) {
  effetActif = fn
  fn()
  effetActif = null
}
 
const etat = reactive({ compteur: 0 })
effect(() => console.log(`compteur = ${etat.compteur}`))   // compteur = 0
etat.compteur++                                            // compteur = 1
etat.compteur++                                            // compteur = 2

L'effet enregistre les propriétés qu'il lit. Toute écriture sur l'une d'elles le relance. La comparaison des modèles de réactivité de Vue et React est détaillée dans React contre Vue.

WeakMap, WeakSet, WeakRef

Une WeakMap n'empêche pas ses clés (des objets) d'être libérées par le ramasse-miettes. Idéal pour associer des métadonnées à un objet sans provoquer de fuite mémoire.

const metadonnees = new WeakMap()
 
function marquer(element, info) {
  metadonnees.set(element, info)
}
// quand element est retiré du DOM et plus référencé, l'entrée disparaît

Pas de .size, pas d'itération : c'est le prix de cette propriété.

WeakRef et FinalizationRegistry vont plus loin mais leur comportement dépend du ramasse-miettes et n'est pas déterministe. Je les réserve aux caches de bibliothèques bas niveau.

Décorateurs (proposition TC39)

Les décorateurs sont en phase finale de standardisation et déjà utilisables avec TypeScript 5+ et Babel :

function journaliser(methode, contexte) {
  return function (...args) {
    console.log(`→ ${contexte.name}(${args.join(', ')})`)
    return methode.apply(this, args)
  }
}
 
class Calcul {
  @journaliser
  additionner(a, b) {
    return a + b
  }
}

Exercice : un observable minimal

Écrire une fonction observable(obj, onChange) qui renvoie un proxy profond : une modification à n'importe quelle profondeur appelle onChange(chemin, valeur).

function observable(obj, onChange, chemin = []) {
  return new Proxy(obj, {
    get(o, p, r) {
      const v = Reflect.get(o, p, r)
      return v !== null && typeof v === 'object' ? observable(v, onChange, [...chemin, p]) : v
    },
    set(o, p, v, r) {
      const ok = Reflect.set(o, p, v, r)
      onChange([...chemin, p].join('.'), v)
      return ok
    },
  })
}
 
const state = observable({ user: { prefs: { theme: 'clair' } } }, (c, v) => console.log(c, '→', v))
state.user.prefs.theme = 'sombre'    // user.prefs.theme → sombre

Amélioration à faire : mettre les proxies en cache dans une WeakMap pour ne pas recréer un proxy à chaque lecture.


Itérateurs pour le parcours, Proxy pour l'interception, WeakMap pour les métadonnées. Ce sont les briques avec lesquelles les frameworks sont construits. On les écrit rarement dans du code applicatif, mais on les rencontre partout dès qu'on ouvre le code d'une bibliothèque.

Précédent
Prototypes, classes et this
Suivant
Niveau expert : moteur, performance, mémoire et architecture

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main