1. Pourquoi Lodash ?

Lodash est une bibliothÚque utilitaire JavaScript proposant plus de 200 fonctions pour manipuler arrays, objets, chaßnes de caractÚres et fonctions. Malgré l'évolution d'ES6+, Lodash reste pertinent pour sa concision, sa lisibilité et ses cas d'usage avancés non couverts nativement.

ES6+ natif vs Lodash — quand utiliser quoi ?

OpérationES6+ natifLodashAvantage Lodash
Map simple[].map(fn)_.map(arr, fn)Fonctionne sur objets aussi
Clone profondNon natif_.cloneDeep(obj)Copie récursive complÚte
Grouper par cléNon natif_.groupBy(arr, key)Une ligne vs 10 lignes
DebounceNon natif_.debounce(fn, ms)Production-ready, testé
AccÚs sécuriséobj?.a?.b?.c_.get(obj, 'a.b.c', défaut)Défaut + chemin dynamique
Fusion profondeSpread partiel_.merge(dest, src)Récursif, pas d'écrasement
Pipeline de donnéesEnchaßnement manuel_.chain(data)...value()Lisible, paresseux

Installation

# Via npm
npm install lodash

# Import partiel (recommandé pour réduire le bundle)
import groupBy from 'lodash/groupBy';
import debounce from 'lodash/debounce';

# Via CDN (navigateur)
<script src="https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js"></script>
# → disponible comme variable globale _
Lodash est disponible via la variable globale _ (underscore). Toutes les fonctions s'appellent avec _.nomFonction(). La bibliothÚque pÚse environ 24KB minifiée + gzippée.

2. Collections

Les fonctions de collection fonctionnent sur les arrays ET les objets, ce qui les rend plus universelles que leurs Ă©quivalents natifs. Elles sont au cƓur de la manipulation de donnĂ©es.

_.map() — Transformer chaque Ă©lĂ©ment

const utilisateurs = [
  { id: 1, nom: 'Alice', age: 28 },
  { id: 2, nom: 'Bob', age: 35 },
  { id: 3, nom: 'Carol', age: 22 }
];

// Extraire une propriété
const noms = _.map(utilisateurs, 'nom');
// ['Alice', 'Bob', 'Carol']

// Transformer avec une fonction
const ages = _.map(utilisateurs, u => u.age * 2);
// [56, 70, 44]

// Fonctionne aussi sur les objets
const scores = { alice: 85, bob: 92, carol: 78 };
const passesSeuil = _.map(scores, (score, nom) => `${nom}: ${score >= 80 ? 'Admis' : 'Recalé'}`);
// ['alice: Admis', 'bob: Admis', 'carol: Recalé']

_.filter() — Garder selon une condition

// Avec une fonction prédicat
const adultes = _.filter(utilisateurs, u => u.age >= 18);

// Avec un objet de correspondance (matching shorthand)
const produits = [
  { id: 1, categorie: 'electronique', actif: true },
  { id: 2, categorie: 'mode', actif: false },
  { id: 3, categorie: 'electronique', actif: true }
];
const electroniquesActifs = _.filter(produits, { categorie: 'electronique', actif: true });
// [{ id: 1, ... }, { id: 3, ... }]

_.find() — Trouver le premier Ă©lĂ©ment

// Retourne le premier élément correspondant, ou undefined
const alice = _.find(utilisateurs, { nom: 'Alice' });
// { id: 1, nom: 'Alice', age: 28 }

const premierAdulte = _.find(utilisateurs, u => u.age > 30);
// { id: 2, nom: 'Bob', age: 35 }

_.reduce() — Accumuler une valeur

const commandes = [
  { produit: 'Livre', prix: 15.90, qte: 2 },
  { produit: 'Stylo', prix: 3.50, qte: 5 },
  { produit: 'Cahier', prix: 8.00, qte: 3 }
];

const totalPanier = _.reduce(commandes, (total, item) => {
  return total + (item.prix * item.qte);
}, 0);
// 15.90*2 + 3.50*5 + 8.00*3 = 73.30

// Construire un objet indexé
const index = _.reduce(utilisateurs, (acc, u) => {
  acc[u.id] = u;
  return acc;
}, {});
// { 1: { id:1, nom:'Alice'... }, 2: ... }

_.forEach() — ItĂ©rer sans retour

// Équivalent à .forEach() natif mais fonctionne sur objets
_.forEach(utilisateurs, (u, index) => {
  console.log(`${index + 1}. ${u.nom} (${u.age} ans)`);
});

// Sur un objet
const config = { host: 'localhost', port: 3000, debug: true };
_.forEach(config, (valeur, clé) => {
  console.log(`${clé} = ${valeur}`);
});

3. Arrays avancés

Lodash enrichit considérablement la manipulation des tableaux avec des fonctions qui nécessiteraient plusieurs lignes de code natif.

_.groupBy() — Regrouper par critùre

const produits = [
  { nom: 'iPhone', categorie: 'Electronique', prix: 899 },
  { nom: 'Jeans', categorie: 'Mode', prix: 59 },
  { nom: 'Laptop', categorie: 'Electronique', prix: 1299 },
  { nom: 'T-shirt', categorie: 'Mode', prix: 25 },
  { nom: 'Casque', categorie: 'Electronique', prix: 199 }
];

const parCategorie = _.groupBy(produits, 'categorie');
// {
//   Electronique: [{ nom: 'iPhone'... }, { nom: 'Laptop'... }, { nom: 'Casque'... }],
//   Mode: [{ nom: 'Jeans'... }, { nom: 'T-shirt'... }]
// }

// Grouper par tranche d'Ăąge
const parTranche = _.groupBy(utilisateurs, u => u.age < 30 ? 'Jeune' : 'Senior');

_.sortBy() — Trier selon des critùres

// Trier par une propriété
const parPrix = _.sortBy(produits, 'prix');
// Du moins cher au plus cher

// Trier par plusieurs critĂšres
const parCatPuisPrix = _.sortBy(produits, ['categorie', 'prix']);
// Alphabétique par catégorie, puis par prix dans chaque catégorie

// Tri décroissant : utiliser _.orderBy
const plusCherEnPremier = _.orderBy(produits, ['prix'], ['desc']);
const multiTri = _.orderBy(produits, ['categorie', 'prix'], ['asc', 'desc']);

_.uniq() et _.uniqBy() — Supprimer les doublons

// Valeurs primitives
const nombres = [1, 2, 2, 3, 1, 4, 3];
const uniques = _.uniq(nombres); // [1, 2, 3, 4]

// Par propriété d'objet
const tags = [
  { id: 1, nom: 'javascript' },
  { id: 2, nom: 'css' },
  { id: 1, nom: 'javascript' } // doublon
];
const tagsUniques = _.uniqBy(tags, 'id'); // garder le premier avec id: 1
// [{ id: 1, nom: 'javascript' }, { id: 2, nom: 'css' }]

_.flatten() et _.flattenDeep()

const matrice = [[1, 2], [3, [4, 5]], [6]];

_.flatten(matrice);    // [1, 2, 3, [4, 5], 6] — un seul niveau
_.flattenDeep(matrice); // [1, 2, 3, 4, 5, 6] — tous les niveaux

_.chunk() — Diviser en pages

const articles = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const pages = _.chunk(articles, 3);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// Pagination pratique
const PAGE_SIZE = 9;
const tousLesProduits = obtenirProduits();
const pages = _.chunk(tousLesProduits, PAGE_SIZE);
const page1 = pages[0]; // 9 premiers produits
const page2 = pages[1]; // produits 10 Ă  18
_.chunk() est idéal pour la pagination cÎté client. Combinez-le avec _.filter() et _.sortBy() pour créer une liste filtrée et paginée entiÚrement en JS.

4. Objets

Lodash propose des utilitaires puissants pour manipuler les objets : sélection de propriétés, clonage profond, fusion récursive et accÚs sécurisé via des chemins de propriétés.

_.pick() et _.omit()

const utilisateur = {
  id: 1,
  nom: 'Alice',
  email: 'alice@example.com',
  motDePasse: 'hash_secret',
  token: 'jwt_123',
  role: 'admin'
};

// Garder seulement certaines propriétés
const profilPublic = _.pick(utilisateur, ['id', 'nom', 'role']);
// { id: 1, nom: 'Alice', role: 'admin' }

// Exclure certaines propriétés (inverse de pick)
const sansMdp = _.omit(utilisateur, ['motDePasse', 'token']);
// { id: 1, nom: 'Alice', email: '...', role: 'admin' }

_.merge() — Fusion rĂ©cursive

const configDefaut = {
  serveur: { host: 'localhost', port: 3000 },
  db: { pool: 5, timeout: 30000 },
  debug: false
};

const configProduction = {
  serveur: { host: 'prod.api.com', port: 443 },
  db: { pool: 20 },
  debug: false
};

// _.merge fusionne récursivement (ne supprime pas les clés non mentionnées)
const config = _.merge({}, configDefaut, configProduction);
// {
//   serveur: { host: 'prod.api.com', port: 443 },  // host ET port mergés
//   db: { pool: 20, timeout: 30000 },               // timeout conservé !
//   debug: false
// }

// ⚠ Object.assign et spread ne font qu'un niveau de profondeur :
// { ...configDefaut, ...configProduction }
// → serveur serait entiĂšrement remplacĂ© (perte de port: 3000)

_.cloneDeep() — Copie profonde

const original = {
  nom: 'Alice',
  adresse: { ville: 'Paris', cp: '75001' },
  tags: ['dev', 'frontend']
};

// Copie superficielle — problĂšme : adresse et tags sont partagĂ©s
const copieShallow = { ...original };
copieShallow.adresse.ville = 'Lyon'; // modifie AUSSI original.adresse.ville !

// Copie profonde — tout est indĂ©pendant
const copieDeep = _.cloneDeep(original);
copieDeep.adresse.ville = 'Lyon';   // original.adresse.ville reste 'Paris' ✅
copieDeep.tags.push('backend');     // original.tags non modifiĂ© ✅

_.get() et _.set() — AccĂšs sĂ©curisĂ©

const data = {
  utilisateur: {
    profil: {
      avatar: { url: 'https://cdn.example.com/alice.jpg' }
    }
  }
};

// Sans Lodash — risque de TypeError si chemin inexistant
// const url = data.utilisateur.profil.avatar.url; // peut crasher

// _.get avec valeur par défaut
const url = _.get(data, 'utilisateur.profil.avatar.url', '/default.jpg');
// 'https://cdn.example.com/alice.jpg'

const manquant = _.get(data, 'utilisateur.preferences.theme', 'dark');
// 'dark' — pas d'erreur mĂȘme si preferences n'existe pas

// _.set — modifier une propriĂ©tĂ© imbriquĂ©e
const nouvellesData = _.cloneDeep(data);
_.set(nouvellesData, 'utilisateur.profil.avatar.url', '/nouveau.jpg');

// AccĂšs par index avec chemin
const premier = _.get({ items: [{ nom: 'A' }] }, 'items[0].nom');
// 'A'
_.merge() modifie l'objet destination. Passez toujours un objet vide {} comme premier argument pour éviter de muter l'objet original : _.merge({}, defaults, overrides)

5. ChaĂźnes & Nombres

Lodash propose des utilitaires pratiques pour formater les chaßnes de caractÚres et manipuler les nombres avec précision.

Fonctions de chaĂźnes

// Mise en forme
_.capitalize('bonjour monde');   // 'Bonjour monde' (seule 1Ăšre lettre)
_.startCase('nomDeFonction');    // 'Nom De Fonction'
_.camelCase('ma variable');      // 'maVariable'
_.kebabCase('Ma Variable');      // 'ma-variable'
_.snakeCase('Ma Variable');      // 'ma_variable'

// _.truncate — tronquer intelligemment
_.truncate('Ce texte est trĂšs long et doit ĂȘtre tronquĂ©', { length: 30 });
// 'Ce texte est trĂšs long et...'

_.truncate('Texte long...', {
  length: 20,
  omission: ' [lire la suite]'
});
// 'Texte... [lire la suite]'

// Vérifications
_.startsWith('Bonjour', 'Bon');  // true
_.endsWith('fichier.js', '.js'); // true
_.includes('hello world', 'world'); // true

Fonctions numériques

// _.round — arrondir avec prĂ©cision
_.round(4.7864, 2);   // 4.79
_.round(4.7864, 0);   // 5
_.round(4789, -2);    // 4800

// _.clamp — contraindre dans un intervalle [min, max]
_.clamp(42, 0, 100);   // 42  (dans l'intervalle)
_.clamp(-5, 0, 100);   // 0   (minimum)
_.clamp(150, 0, 100);  // 100 (maximum)

// Utile pour les barres de progression, volumes...
const volume = _.clamp(valeurUtilisateur, 0, 100);

// _.range — gĂ©nĂ©rer une sĂ©quence
_.range(5);          // [0, 1, 2, 3, 4]
_.range(1, 6);       // [1, 2, 3, 4, 5]
_.range(0, 30, 5);   // [0, 5, 10, 15, 20, 25]
_.range(10, 0, -2);  // [10, 8, 6, 4, 2]

// Générer des pages
const pageNumbers = _.range(1, totalPages + 1); // [1, 2, 3, ...]
_.clamp() est trĂšs utile dans les interfaces utilisateur pour s'assurer qu'une valeur (zoom, volume, pourcentage) reste dans des limites acceptables sans if/else.

6. Fonctions Utilitaires

Les utilitaires de fonctions de Lodash sont parmi les plus précieux pour les performances des interfaces utilisateur et la conception de code robuste.

_.debounce() — Limiter les appels rĂ©pĂ©tĂ©s

// Attendre que l'utilisateur arrĂȘte de taper avant d'envoyer la requĂȘte
const rechercherDebounced = _.debounce(async (terme) => {
  const response = await axios.get('/api/search', { params: { q: terme } });
  afficherResultats(response.data);
}, 300); // attendre 300ms sans nouvelle frappe

document.getElementById('search').addEventListener('input', (e) => {
  rechercherDebounced(e.target.value);
});

// Options avancées
const fn = _.debounce(callback, 500, {
  leading: true,    // exécuter immédiatement la PREMIÈRE fois
  trailing: true,   // exécuter aussi aprÚs le délai
  maxWait: 2000     // forcer l'exĂ©cution aprĂšs 2s mĂȘme si l'utilisateur continue
});

_.throttle() — Limiter la frĂ©quence

// _.throttle garantit une exĂ©cution MAX toutes les N ms (mĂȘme si appelĂ© plus souvent)
const onScroll = _.throttle(() => {
  const scrollY = window.scrollY;
  // Mise Ă  jour barre de progression, sticky header...
}, 100); // au maximum 10 fois par seconde

window.addEventListener('scroll', onScroll);

// Différence debounce vs throttle :
// debounce → attend que les appels s'arrĂȘtent (utile: recherche, resize final)
// throttle → exĂ©cute rĂ©guliĂšrement mĂȘme si les appels continuent (utile: scroll, mouse)

_.once() — ExĂ©cuter une seule fois

// La fonction ne s'exĂ©cute qu'une seule fois, retourne toujours le mĂȘme rĂ©sultat
const initialiser = _.once(() => {
  console.log('Initialisation de la base de données...');
  connexionDB = creerConnexion();
  return connexionDB;
});

initialiser(); // 'Initialisation...' → retourne connexion
initialiser(); // rien affichĂ© → retourne la mĂȘme connexion
initialiser(); // rien affichĂ© → retourne la mĂȘme connexion

_.memoize() — MĂ©moĂŻsation

// Met en cache le résultat pour chaque jeu d'arguments
function calculerFibonacci(n) {
  if (n <= 1) return n;
  return calculerFibonacci(n - 1) + calculerFibonacci(n - 2);
}

const fibMemo = _.memoize(calculerFibonacci);
fibMemo(40); // Calculé une fois (long)
fibMemo(40); // InstantanĂ© — rĂ©sultat mis en cache

// Clé de cache personnalisée (pour arguments objets)
const trouverUtilisateur = _.memoize(
  (id) => fetch(`/api/users/${id}`).then(r => r.json()),
  (id) => `user_${id}` // clé de cache
);

_.chain() — Pipeline de transformations

const donnees = [
  { nom: 'Alice', dept: 'Dev', salaire: 4500 },
  { nom: 'Bob', dept: 'Design', salaire: 3800 },
  { nom: 'Carol', dept: 'Dev', salaire: 5200 },
  { nom: 'Dave', dept: 'Design', salaire: 4100 },
  { nom: 'Eve', dept: 'Dev', salaire: 4800 }
];

// Sans chain — difficile à lire
const resultat = _.map(
  _.sortBy(_.filter(donnees, { dept: 'Dev' }), 'salaire'),
  'nom'
);

// Avec chain — pipeline lisible
const resultat2 = _.chain(donnees)
  .filter({ dept: 'Dev' })
  .sortBy('salaire')
  .map('nom')
  .value(); // IMPORTANT : .value() déclenche l'exécution
// ['Alice', 'Eve', 'Carol']

// Pipeline plus complexe
const stats = _.chain(donnees)
  .groupBy('dept')
  .mapValues(employes => ({
    count: employes.length,
    salaireTotal: _.sumBy(employes, 'salaire'),
    salaireMoyen: _.meanBy(employes, 'salaire')
  }))
  .value();
_.chain() utilise l'évaluation paresseuse : aucun traitement n'est effectué tant que .value() n'est pas appelé. Cela optimise les performances sur les grandes collections.
← Accueil Exercices →