Lodash Complete Guide | JavaScript Utility Library for Arrays, Objects, and More

Lodash Complete Guide | JavaScript Utility Library for Arrays, Objects, and More

이 글의 핵심

Lodash is the most popular JavaScript utility library. It provides functions for arrays, objects, strings, functions, and more — making JavaScript development easier and more consistent.

Introduction

Lodash is a modern JavaScript utility library delivering modularity, performance, and extras. It makes working with arrays, objects, strings, functions, and numbers easier and more consistent.

Why Lodash?

Native JavaScript:

// Deep clone (doesn't work properly)
const clone = JSON.parse(JSON.stringify(obj)); // Loses functions, dates

// Debounce (complex to implement)
let timeout;
function debounce(fn, delay) {
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), delay);
  };
}

With Lodash:

// Deep clone
const clone = _.cloneDeep(obj);

// Debounce
const debouncedFn = _.debounce(fn, delay);

1. Installation

# npm
npm install lodash

# yarn
yarn add lodash

# CDN (browser)
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

Import Methods

// Import entire library
import _ from 'lodash';

// Import individual functions (recommended for tree-shaking)
import debounce from 'lodash/debounce';
import cloneDeep from 'lodash/cloneDeep';

// CommonJS
const _ = require('lodash');
const debounce = require('lodash/debounce');

2. Array Methods

Map, Filter, Reduce

const users = [
  { id: 1, name: 'Alice', age: 30, active: true },
  { id: 2, name: 'Bob', age: 25, active: false },
  { id: 3, name: 'Charlie', age: 35, active: true },
];

// Map
_.map(users, 'name'); // ['Alice', 'Bob', 'Charlie']
_.map(users, user => user.age * 2); // [60, 50, 70]

// Filter
_.filter(users, { active: true }); // Active users
_.filter(users, user => user.age > 30); // Age > 30

// Find
_.find(users, { name: 'Alice' }); // First match
_.findLast(users, { active: true }); // Last match

// Reduce
_.reduce(users, (sum, user) => sum + user.age, 0); // Total age: 90

Unique, Flatten, Chunk

// Unique
_.uniq([1, 2, 2, 3, 3, 4]); // [1, 2, 3, 4]
_.uniqBy([{ x: 1 }, { x: 2 }, { x: 1 }], 'x'); // [{ x: 1 }, { x: 2 }]

// Flatten
_.flatten([1, [2, [3, [4]]]]); // [1, 2, [3, [4]]]
_.flattenDeep([1, [2, [3, [4]]]]); // [1, 2, 3, 4]

// Chunk (split into groups)
_.chunk([1, 2, 3, 4, 5, 6], 2); // [[1, 2], [3, 4], [5, 6]]
_.chunk(['a', 'b', 'c', 'd'], 3); // [['a', 'b', 'c'], ['d']]

// Compact (remove falsy values)
_.compact([0, 1, false, 2, '', 3, null, undefined]); // [1, 2, 3]

Difference, Intersection, Union

const arr1 = [1, 2, 3];
const arr2 = [2, 3, 4];

// Difference (in arr1 but not arr2)
_.difference(arr1, arr2); // [1]

// Intersection (in both)
_.intersection(arr1, arr2); // [2, 3]

// Union (all unique values)
_.union(arr1, arr2); // [1, 2, 3, 4]

// With objects
const users1 = [{ id: 1 }, { id: 2 }];
const users2 = [{ id: 2 }, { id: 3 }];

_.differenceBy(users1, users2, 'id'); // [{ id: 1 }]
_.intersectionBy(users1, users2, 'id'); // [{ id: 2 }]

Sorting

const users = [
  { name: 'Charlie', age: 35 },
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
];

// Sort by single property
_.sortBy(users, 'age');
// [{ name: 'Bob', age: 25 }, { name: 'Alice', age: 30 }, { name: 'Charlie', age: 35 }]

// Sort by multiple properties
_.sortBy(users, ['age', 'name']);

// Sort with custom order
_.orderBy(users, ['age'], ['desc']);
// [{ name: 'Charlie', age: 35 }, ...]

Take, Drop, Sample

const arr = [1, 2, 3, 4, 5];

// Take first n
_.take(arr, 2); // [1, 2]
_.takeRight(arr, 2); // [4, 5]

// Drop first n
_.drop(arr, 2); // [3, 4, 5]
_.dropRight(arr, 2); // [1, 2, 3]

// Random sample
_.sample(arr); // Random element
_.sampleSize(arr, 2); // 2 random elements

// Shuffle
_.shuffle([1, 2, 3, 4, 5]); // Random order

3. Object Methods

Get, Set, Has

const user = {
  name: 'Alice',
  address: {
    city: 'New York',
    zip: '10001',
  },
};

// Get (safe nested access)
_.get(user, 'address.city'); // 'New York'
_.get(user, 'address.country', 'USA'); // 'USA' (default)
_.get(user, 'profile.bio'); // undefined

// Set (create nested path)
_.set(user, 'profile.bio', 'Developer');
// { name: 'Alice', address: {...}, profile: { bio: 'Developer' } }

// Has
_.has(user, 'address.city'); // true
_.has(user, 'address.country'); // false

Pick, Omit

const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  password: 'secret',
  role: 'admin',
};

// Pick (select properties)
_.pick(user, ['id', 'name', 'email']);
// { id: 1, name: 'Alice', email: 'alice@example.com' }

// Omit (exclude properties)
_.omit(user, ['password', 'role']);
// { id: 1, name: 'Alice', email: 'alice@example.com' }

Merge, Assign

const obj1 = { a: 1, b: { x: 1 } };
const obj2 = { b: { y: 2 }, c: 3 };

// Merge (deep)
_.merge(obj1, obj2);
// { a: 1, b: { x: 1, y: 2 }, c: 3 }

// Assign (shallow)
_.assign({}, obj1, obj2);
// { a: 1, b: { y: 2 }, c: 3 }

Keys, Values, Entries

const obj = { a: 1, b: 2, c: 3 };

// Keys
_.keys(obj); // ['a', 'b', 'c']

// Values
_.values(obj); // [1, 2, 3]

// Entries (key-value pairs)
_.toPairs(obj); // [['a', 1], ['b', 2], ['c', 3]]

// From entries
_.fromPairs([['a', 1], ['b', 2]]); // { a: 1, b: 2 }

Clone

const obj = {
  name: 'Alice',
  address: { city: 'NYC' },
  hobbies: ['coding', 'reading'],
};

// Shallow clone
const shallow = _.clone(obj);
shallow.address.city = 'LA'; // Affects original!

// Deep clone
const deep = _.cloneDeep(obj);
deep.address.city = 'LA'; // Doesn't affect original

4. Collection Methods

const users = [
  { name: 'Alice', age: 30, active: true },
  { name: 'Bob', age: 25, active: false },
  { name: 'Charlie', age: 35, active: true },
];

// Group by
_.groupBy(users, 'active');
// {
//   true: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
//   false: [{ name: 'Bob', ... }]
// }

// Count by
_.countBy(users, 'active'); // { true: 2, false: 1 }

// Key by (index by property)
_.keyBy(users, 'name');
// {
//   Alice: { name: 'Alice', ... },
//   Bob: { name: 'Bob', ... },
//   Charlie: { name: 'Charlie', ... }
// }

// Partition (split by condition)
_.partition(users, { active: true });
// [[active users], [inactive users]]

5. Function Utilities

Debounce

// Debounce (wait until user stops typing)
const search = _.debounce((query) => {
  console.log('Searching for:', query);
}, 300);

// User types: a, ab, abc
// Only searches once after 300ms of no typing

// Cancel pending debounce
search.cancel();

// Execute immediately
search.flush();

Throttle

// Throttle (execute at most once per interval)
const handleScroll = _.throttle(() => {
  console.log('Scrolling');
}, 100);

window.addEventListener('scroll', handleScroll);

// Execute at most once per 100ms

// Cancel
handleScroll.cancel();

Once

// Execute only once
const initialize = _.once(() => {
  console.log('Initialized');
});

initialize(); // Logs: "Initialized"
initialize(); // Does nothing
initialize(); // Does nothing

Memoize

// Cache function results
const fibonacci = _.memoize((n) => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

fibonacci(40); // First call: slow
fibonacci(40); // Cached: instant

// Custom cache key
const getUser = _.memoize(
  async (id) => {
    const response = await fetch(`/users/${id}`);
    return response.json();
  },
  (id) => `user-${id}` // Cache key
);

Curry

// Curry (partial application)
const add = (a, b, c) => a + b + c;
const curriedAdd = _.curry(add);

curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6

// Useful for reusable functions
const add5 = curriedAdd(5);
add5(10, 15); // 30

6. String Methods

// Case conversion
_.camelCase('hello world'); // 'helloWorld'
_.snakeCase('helloWorld'); // 'hello_world'
_.kebabCase('helloWorld'); // 'hello-world'
_.startCase('hello world'); // 'Hello World'

// Truncate
_.truncate('This is a very long string', { length: 15 });
// 'This is a ve...'

// Pad
_.pad('hi', 8); // '   hi   '
_.padStart('5', 3, '0'); // '005'
_.padEnd('5', 3, '0'); // '500'

// Trim
_.trim('  hello  '); // 'hello'
_.trimStart('  hello  '); // 'hello  '
_.trimEnd('  hello  '); // '  hello'

7. Number Methods

// Random
_.random(1, 10); // Random int between 1 and 10
_.random(1.5, 5.5, true); // Random float

// Clamp (constrain to range)
_.clamp(10, 0, 5); // 5
_.clamp(-5, 0, 10); // 0
_.clamp(3, 0, 10); // 3

// In range
_.inRange(3, 2, 4); // true
_.inRange(5, 8); // true (0 to 8)

8. Real-World Examples

API Request with Debounce

import debounce from 'lodash/debounce';
import axios from 'axios';

const searchAPI = async (query) => {
  const response = await axios.get('/api/search', {
    params: { q: query }
  });
  return response.data;
};

// Debounce to avoid excessive API calls
const debouncedSearch = debounce(searchAPI, 300);

// React example
function SearchInput() {
  const [results, setResults] = useState([]);

  const handleChange = async (e) => {
    const query = e.target.value;
    if (query.length > 2) {
      const data = await debouncedSearch(query);
      setResults(data);
    }
  };

  return <input onChange={handleChange} />;
}

Data Transformation

import { groupBy, mapValues, sortBy } from 'lodash';

const orders = [
  { id: 1, customer: 'Alice', amount: 100, status: 'completed' },
  { id: 2, customer: 'Bob', amount: 50, status: 'pending' },
  { id: 3, customer: 'Alice', amount: 200, status: 'completed' },
];

// Group by customer and sum amounts
const customerTotals = mapValues(
  groupBy(orders, 'customer'),
  (orders) => orders.reduce((sum, order) => sum + order.amount, 0)
);
// { Alice: 300, Bob: 50 }

// Get top customers
const topCustomers = sortBy(
  Object.entries(customerTotals),
  ([_, amount]) => -amount
);
// [['Alice', 300], ['Bob', 50]]

Safe Object Access

import get from 'lodash/get';

// API response
const response = {
  data: {
    user: {
      profile: {
        email: 'alice@example.com'
      }
    }
  }
};

// Safe access (no errors if path doesn't exist)
const email = get(response, 'data.user.profile.email');
const phone = get(response, 'data.user.profile.phone', 'N/A');

// Instead of:
// const email = response?.data?.user?.profile?.email;

9. TypeScript Support

import { debounce, cloneDeep } from 'lodash';

interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

// Type-safe clone
const cloned: User[] = cloneDeep(users);

// Type-safe debounce
const handleSearch = debounce((query: string) => {
  console.log(query);
}, 300);

10. Bundle Size Optimization

// Bad: imports entire library (~70KB)
import _ from 'lodash';
_.debounce(fn, 300);

// Good: import only what you need
import debounce from 'lodash/debounce';
debounce(fn, 300);

// Even better: use lodash-es for tree-shaking
import { debounce } from 'lodash-es';

11. Performance Tips

1. Chain Operations

// Good: chain for efficiency
_.chain(users)
  .filter({ active: true })
  .sortBy('age')
  .take(10)
  .value();

// Bad: multiple operations
let result = _.filter(users, { active: true });
result = _.sortBy(result, 'age');
result = _.take(result, 10);

2. Reuse Functions

// Good: create once, use many times
const debouncedSearch = _.debounce(search, 300);

// Bad: create new function every time
onClick={() => _.debounce(search, 300)()}

Summary

Lodash simplifies JavaScript development:

  • Array/Object manipulation
  • Function utilities (debounce, throttle, memoize)
  • Deep cloning and safe object access
  • String formatting
  • Cross-browser consistency

Key Takeaways:

  1. Import individual functions to reduce bundle size
  2. Use debounce/throttle for performance
  3. Deep clone with _.cloneDeep
  4. Safe nested access with _.get
  5. Group/transform data with collection methods

Next Steps:

  • Compare Ramda
  • Learn JavaScript
  • Optimize with Webpack

Resources: