Aller au contenu principal

Paradigmes de programmation en JavaScript

Un paradigme est une manière d’organiser un raisonnement et le changement d’état. Un même langage peut en permettre plusieurs, et un même programme peut les combiner. Le choix utile dépend du problème, des contraintes et de la preuve attendue — pas d’une hiérarchie universelle.

Un même problème, trois organisations

Problème : à partir de notes potentiellement invalides, conserver les nombres de 0 à 20 et calculer leur moyenne. Pour [12, "absent", 16, 25], le résultat attendu est 14.

Style impératif

Le code décrit les étapes et modifie des variables locales :

function averageValidImperative(values) {
let total = 0;
let count = 0;

for (const value of values) {
if (typeof value === 'number' && value >= 0 && value <= 20) {
total += value;
count += 1;
}
}

if (count === 0) return null;
return total / count;
}

L’état change dans total et count. Cette forme rend l’ordre des opérations explicite.

Style fonctionnel

Le code compose des transformations et évite de modifier la collection reçue :

const isValidGrade = (value) =>
typeof value === 'number' && value >= 0 && value <= 20;

function averageValidFunctional(values) {
const valid = values.filter(isValidGrade);
if (valid.length === 0) return null;
return valid.reduce((sum, value) => sum + value, 0) / valid.length;
}

filter et reduce déclarent les transformations. Elles ne rendent pas la fonction automatiquement pure ou rapide : il faut toujours examiner les effets et mesurer le contexte réel.

Style orienté objet

Le code regroupe les données d’un relevé et les opérations qui maintiennent son contrat :

class GradeBook {
constructor(values) {
this.values = [...values];
}

averageValid() {
const valid = this.values.filter(isValidGrade);
if (valid.length === 0) return null;
return valid.reduce((sum, value) => sum + value, 0) / valid.length;
}
}

Cette organisation devient utile si le relevé possède plusieurs règles et opérations cohérentes. Créer une classe pour une seule fonction n’apporte pas automatiquement un meilleur modèle.

Les trois versions doivent respecter le même contrat observable :

const input = [12, 'absent', 16, 25];
console.assert(averageValidImperative(input) === 14);
console.assert(averageValidFunctional(input) === 14);
console.assert(new GradeBook(input).averageValid() === 14);

Impératif, déclaratif, fonctionnel et objet

  • Impératif : décrit une suite de commandes et de changements d’état.
  • Déclaratif : exprime le résultat ou les contraintes, en laissant une autre couche choisir des étapes. Une requête SQL est un exemple courant.
  • Fonctionnel : privilégie la composition de fonctions, les valeurs immuables et la maîtrise des effets. JavaScript permet ce style sans l’imposer.
  • Orienté objet : organise données et comportements autour d’objets et de leurs responsabilités. Héritage n’est qu’un mécanisme possible ; la composition reste souvent plus simple.

Valeurs primitives et objets en JavaScript

JavaScript possède sept types primitifs : string, number, bigint, boolean, undefined, symbol et null. Une valeur primitive n’est pas un objet et ne reçoit pas de propriété persistante. Quand on écrit "dev".toUpperCase(), le langage fournit temporairement l’accès aux méthodes du prototype correspondant.

Tout le reste est objet, notamment les tableaux et les fonctions. Attention à deux détails historiques : typeof null renvoie "object", même si null est une primitive ; une fonction renvoie "function" avec typeof, tout en restant un objet auquel des propriétés peuvent être attachées.

typeof 'dev'; // "string"
typeof null; // "object" — particularité historique
typeof function () {}; // "function"
Array.isArray([]); // true

Prototypes et classes

Chaque objet ordinaire peut déléguer la recherche de propriétés à un prototype. Cette chaîne existe encore lorsque la syntaxe class est utilisée.

const gradeBookMethods = {
size() {
return this.values.length;
},
};

const book = Object.create(gradeBookMethods);
book.values = [12, 16];
console.assert(book.size() === 2);
console.assert(Object.getPrototypeOf(book) === gradeBookMethods);

La syntaxe class fournit des règles et une écriture plus structurée pour les constructeurs, méthodes, champs privés et héritage. Elle ne remplace pas le modèle prototype : les méthodes de GradeBook se trouvent sur GradeBook.prototype.

console.assert(
Object.getPrototypeOf(new GradeBook([])) === GradeBook.prototype,
);

Choisir à partir d’une contrainte

Demande d’abord : quel état change, qui en est responsable, quel contrat doit rester vrai et comment l’observer ? Une boucle impérative peut être la forme la plus lisible ; une chaîne fonctionnelle peut rendre une transformation claire ; un objet peut protéger un invariant partagé. Aucun de ces choix ne garantit à lui seul performance, testabilité ou maintenabilité.

Vérification observable

Implémente les trois versions de la moyenne, puis vérifie :

const cases = [
{input: [12, 'absent', 16, 25], expected: 14},
{input: [], expected: null},
{input: [-1, 21], expected: null},
];

La vérification est réussie si les trois versions donnent le résultat attendu, ne modifient pas input et si tu peux montrer, pour chacune, où l’état se trouve et quel mécanisme organise le calcul.