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ération | ES6+ natif | Lodash | Avantage Lodash |
|---|---|---|---|
| Map simple | [].map(fn) | _.map(arr, fn) | Fonctionne sur objets aussi |
| Clone profond | Non natif | _.cloneDeep(obj) | Copie récursive complÚte |
| Grouper par clé | Non natif | _.groupBy(arr, key) | Une ligne vs 10 lignes |
| Debounce | Non 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 profonde | Spread partiel | _.merge(dest, src) | Récursif, pas d'écrasement |
| Pipeline de données | Enchaß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 _
_ (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'
{}
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.