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 9·50 min

Prototypes, classes et this

JavaScript n'est pas un langage à classes. C'est un langage à prototypes : un objet délègue ce qu'il ne sait pas faire à un autre objet. Le mot-clé class, arrivé en 2015, n'est qu'une syntaxe plus familière posée sur ce mécanisme. Et this, souvent présenté comme imprévisible, suit en réalité quatre règles précises.

La chaîne de prototypes

Chaque objet a un lien interne [[Prototype]] vers un autre objet (ou null). Quand on lit une propriété absente, JavaScript la cherche en remontant cette chaîne.

const animal = {
  respirer() {
    return `${this.nom} respire`
  },
}
 
const chien = Object.create(animal)   // chien → animal → Object.prototype → null
chien.nom = 'Rex'
chien.aboyer = function () {
  return 'Wouf'
}
 
chien.respirer()                     // 'Rex respire' — trouvé sur animal
Object.getPrototypeOf(chien) === animal   // true
Object.hasOwn(chien, 'respirer')     // false

L'écriture, elle, ne remonte pas : chien.respirer = ... crée une propriété propre qui masque celle du prototype.

Les tableaux héritent de Array.prototype, qui hérite de Object.prototype :

const arr = [1, 2]
Object.getPrototypeOf(arr) === Array.prototype                    // true
Object.getPrototypeOf(Array.prototype) === Object.prototype       // true

C'est pour cela que arr.map existe sans être défini sur arr.

Fonctions constructeurs

Avant class, on écrivait :

function Personne(nom) {
  this.nom = nom
}
Personne.prototype.saluer = function () {
  return `Je suis ${this.nom}`
}
 
const ada = new Personne('Ada')

new fait quatre choses : crée un objet vide, lie son prototype à Personne.prototype, appelle la fonction avec this = ce nouvel objet, renvoie l'objet.

Classes

La syntaxe class produit exactement la même structure, avec des garde-fous en plus (appel sans new interdit, mode strict).

class Compte {
  static #nbComptes = 0          // champ statique privé
  #solde = 0                     // champ privé
  titulaire
 
  constructor(titulaire, soldeInitial = 0) {
    this.titulaire = titulaire
    this.#solde = soldeInitial
    Compte.#nbComptes++
  }
 
  deposer(montant) {
    if (montant <= 0) throw new RangeError('Montant positif requis')
    this.#solde += montant
    return this
  }
 
  get solde() {
    return this.#solde
  }
 
  static get total() {
    return Compte.#nbComptes
  }
 
  toString() {
    return `${this.titulaire} : ${this.#solde} €`
  }
}
 
const c = new Compte('Ada').deposer(100).deposer(50)
c.solde        // 150
c.#solde       // SyntaxError — réellement privé
Compte.total   // 1
typeof Compte  // 'function'

Les champs # sont vraiment inaccessibles de l'extérieur, contrairement à la convention _prive.

Héritage

class CompteEpargne extends Compte {
  constructor(titulaire, taux) {
    super(titulaire)            // obligatoire avant d'utiliser this
    this.taux = taux
  }
 
  appliquerInterets() {
    return this.deposer(this.solde * this.taux)
  }
 
  toString() {
    return `${super.toString()} (épargne ${this.taux * 100} %)`
  }
}

Étendre les classes natives

class HttpError extends Error {
  constructor(status, message, options) {
    super(message, options)     // options.cause pour chaîner
    this.name = 'HttpError'
    this.status = status
  }
}
 
try {
  throw new HttpError(404, 'Article introuvable')
} catch (err) {
  if (err instanceof HttpError && err.status === 404) afficher404()
  else throw err
}

Les quatre règles de this

this n'est pas lié à l'endroit où la fonction est écrite, mais à la façon dont elle est appelée. Sauf pour les fléchées.

AppelValeur de this
new F()le nouvel objet
f.call(obj), f.apply(obj), f.bind(obj)obj
obj.f()obj
f()undefined en mode strict (globalThis sinon)
fonction fléchéethis de la portée englobante, figé

Le piège classique : perdre this

class Minuteur {
  secondes = 0
  demarrer() {
    setInterval(function () {
      this.secondes++       // TypeError : this est undefined
    }, 1000)
  }
}

La fonction est appelée par setInterval, sans objet devant le point. Trois corrections :

// 1. Fonction fléchée — this hérité de demarrer()
setInterval(() => this.secondes++, 1000)
 
// 2. bind
setInterval(function () { this.secondes++ }.bind(this), 1000)
 
// 3. Champ de classe fléché — utile pour les handlers passés en callback
class Bouton {
  clics = 0
  handleClick = () => {
    this.clics++
  }
}
el.addEventListener('click', new Bouton().handleClick)

Même problème en extrayant une méthode :

const { deposer } = c
deposer(10)    // TypeError : this est undefined

call, apply, bind

function presenter(salutation, ponctuation) {
  return `${salutation}, je suis ${this.nom}${ponctuation}`
}
 
const ada = { nom: 'Ada' }
presenter.call(ada, 'Bonjour', '!')
presenter.apply(ada, ['Bonjour', '!'])
const presenterAda = presenter.bind(ada, 'Salut')
presenterAda('.')   // 'Salut, je suis Ada.'

Getters, setters et propriétés

const produit = {
  _prix: 10,
  get prixTTC() {
    return this._prix * 1.2
  },
  set prix(v) {
    if (v < 0) throw new RangeError('Prix négatif')
    this._prix = v
  },
}
 
Object.defineProperty(produit, 'id', {
  value: 42,
  writable: false,
  enumerable: false,
  configurable: false,
})
Object.getOwnPropertyDescriptor(produit, 'id')

Chaque propriété a un descripteur : value, writable, enumerable, configurable (ou get / set). Object.freeze passe simplement toutes les propriétés en non modifiables.

Mixins et composition

L'héritage simple limite. Pour partager des comportements entre classes sans lien, une fonction qui renvoie une classe :

const Serialisable = Base => class extends Base {
  toJSON() {
    return { type: this.constructor.name, ...this }
  }
}
 
const Horodate = Base => class extends Base {
  cree = new Date()
}
 
class Article extends Serialisable(Horodate(Object)) {
  constructor(titre) {
    super()
    this.titre = titre
  }
}

En pratique, je préfère la composition simple : un objet qui contient d'autres objets, plutôt qu'une pile de classes.

Exercice : un EventEmitter

Implémenter une classe avec on, off, once et emit.

class Emitter {
  #handlers = new Map()
 
  on(event, fn) {
    if (!this.#handlers.has(event)) this.#handlers.set(event, new Set())
    this.#handlers.get(event).add(fn)
    return () => this.off(event, fn)       // renvoie une fonction de désinscription
  }
 
  off(event, fn) {
    this.#handlers.get(event)?.delete(fn)
  }
 
  once(event, fn) {
    const off = this.on(event, (...args) => {
      off()
      fn(...args)
    })
    return off
  }
 
  emit(event, ...args) {
    for (const fn of [...(this.#handlers.get(event) ?? [])]) fn(...args)
  }
}
 
const bus = new Emitter()
const stop = bus.on('message', m => console.log('reçu', m))
bus.once('message', m => console.log('une seule fois', m))
bus.emit('message', 'a')   // reçu a, une seule fois a
bus.emit('message', 'b')   // reçu b
stop()
bus.emit('message', 'c')   // rien

La copie [...set] dans emit évite de modifier l'ensemble pendant qu'on le parcourt, ce qui arrive justement avec once.


Sous la syntaxe class il n'y a que des objets qui délèguent à d'autres objets. Et this dépend de l'appel, pas de la définition. Ces deux faits expliquent la quasi-totalité des surprises orientées objet en JavaScript.

Précédent
Modules, npm, outillage et tests
Suivant
Itérateurs, générateurs, Symbol, Proxy et métaprogrammation

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