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.