跳到主要内容

Lodash 完整教学指南

目录

  1. 简介
  2. 安装与引入
  3. 数组方法
  4. 对象方法
  5. 集合方法
  6. 函数方法
  7. 字符串方法
  8. 实用工具方法
  9. 最佳实践

简介

Lodash 是一个现代化的 JavaScript 实用工具库,提供了模块化、高性能的函数来操作数组、对象、字符串等数据类型。它简化了常见的编程任务,提高了代码的可读性和可维护性。

为什么使用 Lodash?

  • 一致性:跨浏览器提供一致的 API
  • 性能优化:内部实现经过性能优化
  • 链式调用:支持优雅的链式操作
  • 模块化:可按需引入特定功能

安装与引入

NPM 安装

npm install lodash

引入方式

// 完整引入
import _ from 'lodash';

// 按需引入(推荐)
import debounce from 'lodash/debounce';
import map from 'lodash/map';

// ES6 解构引入
import { debounce, throttle, cloneDeep } from 'lodash';

CDN 引入

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

数组方法

1. chunk - 数组分块

将数组分割成指定大小的块。

const array = [1, 2, 3, 4, 5, 6, 7];
_.chunk(array, 3);
// 结果: [[1, 2, 3], [4, 5, 6], [7]]

// 实际应用:分页数据
const items = [...Array(100).keys()];
const pages = _.chunk(items, 10); // 每页10条

2. compact - 移除假值

移除数组中的 false、null、0、""、undefined 和 NaN。

_.compact([0, 1, false, 2, '', 3, null, undefined, NaN]);
// 结果: [1, 2, 3]

// 实际应用:清理表单数据
const formData = ['name', '', 'email', null, 'phone'];
const cleanData = _.compact(formData);

3. difference - 数组差集

找出第一个数组中不存在于其他数组的元素。

_.difference([2, 1, 3, 4], [2, 3]);
// 结果: [1, 4]

// 实际应用:找出已删除的项目
const oldIds = [1, 2, 3, 4, 5];
const newIds = [1, 3, 5];
const deletedIds = _.difference(oldIds, newIds); // [2, 4]

4. flatten & flattenDeep - 数组扁平化

// 扁平化一层
_.flatten([1, [2, [3, [4]], 5]]);
// 结果: [1, 2, [3, [4]], 5]

// 完全扁平化
_.flattenDeep([1, [2, [3, [4]], 5]]);
// 结果: [1, 2, 3, 4, 5]

// 实际应用:处理嵌套分类
const categories = [
{ name: '电子产品', subcategories: ['手机', '电脑'] },
{ name: '服装', subcategories: ['男装', '女装'] }
];
const allCategories = _.flattenDeep(categories.map(c => c.subcategories));

5. uniq & uniqBy - 数组去重

// 简单去重
_.uniq([2, 1, 2, 3, 1]);
// 结果: [2, 1, 3]

// 根据属性去重
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }
];
_.uniqBy(users, 'id');
// 结果: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

6. intersection - 数组交集

_.intersection([2, 1], [2, 3], [2, 4]);
// 结果: [2]

// 实际应用:查找共同兴趣
const user1Interests = ['sports', 'music', 'reading'];
const user2Interests = ['music', 'movies', 'reading'];
const commonInterests = _.intersection(user1Interests, user2Interests);
// 结果: ['music', 'reading']

7. zip & unzip - 数组打包与解包

// 打包
_.zip(['a', 'b'], [1, 2], [true, false]);
// 结果: [['a', 1, true], ['b', 2, false]]

// 解包
_.unzip([['a', 1, true], ['b', 2, false]]);
// 结果: [['a', 'b'], [1, 2], [true, false]]

对象方法

1. get - 安全访问对象属性

const object = { a: { b: { c: 3 } } };

_.get(object, 'a.b.c');
// 结果: 3

_.get(object, 'a.b.x', 'default');
// 结果: 'default'

// 实际应用:访问 API 响应
const response = { data: { user: { profile: { name: 'John' } } } };
const userName = _.get(response, 'data.user.profile.name', 'Unknown');

2. set - 设置对象属性

const object = { a: { b: { c: 3 } } };

_.set(object, 'a.b.c', 4);
// object.a.b.c 现在是 4

_.set(object, 'x[0].y', 5);
// 创建新路径: { a: {...}, x: [{ y: 5 }] }

3. pick & omit - 选择/排除属性

const object = { a: 1, b: 2, c: 3, d: 4 };

// 选择属性
_.pick(object, ['a', 'c']);
// 结果: { a: 1, c: 3 }

// 排除属性
_.omit(object, ['a', 'c']);
// 结果: { b: 2, d: 4 }

// 实际应用:清理敏感数据
const user = { id: 1, name: 'John', password: '123', email: 'john@example.com' };
const safeUser = _.omit(user, ['password']);

4. merge & mergeWith - 深度合并对象

const object1 = { a: 1, b: { x: 1, y: 2 } };
const object2 = { b: { y: 3, z: 4 }, c: 5 };

_.merge(object1, object2);
// 结果: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }

// 自定义合并策略
_.mergeWith(object1, object2, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return objValue.concat(srcValue);
}
});

5. cloneDeep - 深拷贝

const original = { 
a: 1,
b: { c: 2 },
d: [1, 2, 3]
};

const copy = _.cloneDeep(original);
copy.b.c = 999;
// original.b.c 仍然是 2

// 实际应用:避免状态污染
const initialState = { user: { name: 'John', settings: {} } };
const newState = _.cloneDeep(initialState);

6. mapKeys & mapValues - 转换键值

// 转换键
_.mapKeys({ a: 1, b: 2 }, (value, key) => key + value);
// 结果: { a1: 1, b2: 2 }

// 转换值
_.mapValues({ a: 1, b: 2 }, value => value * 2);
// 结果: { a: 2, b: 4 }

// 实际应用:数据格式转换
const prices = { apple: '10', banana: '5', orange: '8' };
const numericPrices = _.mapValues(prices, Number);

集合方法

1. groupBy - 分组

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

_.groupBy(users, 'age');
// 结果: {
// 25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
// 30: [{ name: 'Bob', age: 30 }]
// }

// 实际应用:订单按状态分组
const orders = [
{ id: 1, status: 'pending' },
{ id: 2, status: 'completed' },
{ id: 3, status: 'pending' }
];
const ordersByStatus = _.groupBy(orders, 'status');

2. sortBy & orderBy - 排序

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

// 单一条件排序
_.sortBy(users, 'age');

// 多条件排序
_.orderBy(users, ['age', 'name'], ['desc', 'asc']);
// 先按年龄降序,再按名字升序

3. filter & reject - 过滤

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

// 保留符合条件的
_.filter(users, { active: true });
// 结果: [{ name: 'Alice', active: true }, { name: 'Charlie', active: true }]

// 排除符合条件的
_.reject(users, { active: true });
// 结果: [{ name: 'Bob', active: false }]

4. find & findLast - 查找

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

_.find(users, { name: 'Alice' });
// 结果: { id: 1, name: 'Alice' } (第一个匹配)

_.findLast(users, { name: 'Alice' });
// 结果: { id: 3, name: 'Alice' } (最后一个匹配)

5. reduce - 归约

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

_.reduce(numbers, (sum, n) => sum + n, 0);
// 结果: 15

// 实际应用:计算购物车总价
const cart = [
{ name: 'item1', price: 10, quantity: 2 },
{ name: 'item2', price: 20, quantity: 1 }
];
const total = _.reduce(cart, (sum, item) => sum + item.price * item.quantity, 0);
// 结果: 40

函数方法

1. debounce - 防抖

函数在停止调用后延迟执行。

// 搜索框输入防抖
const searchAPI = (query) => {
console.log('Searching for:', query);
};

const debouncedSearch = _.debounce(searchAPI, 500);

// 用户快速输入时,只在停止输入500ms后执行
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});

2. throttle - 节流

函数在指定时间内最多执行一次。

// 滚动事件节流
const handleScroll = () => {
console.log('Scroll position:', window.scrollY);
};

const throttledScroll = _.throttle(handleScroll, 1000);

// 无论滚动多快,最多每秒执行一次
window.addEventListener('scroll', throttledScroll);

3. once - 只执行一次

const initialize = _.once(() => {
console.log('Initialized!');
// 执行初始化逻辑
});

initialize(); // 输出: Initialized!
initialize(); // 不执行
initialize(); // 不执行

// 实际应用:单例模式
const createConnection = _.once(() => {
return { /* connection object */ };
});

4. memoize - 缓存结果

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

fibonacci(100); // 计算并缓存
fibonacci(100); // 直接返回缓存结果

// 自定义缓存键
const expensiveOperation = _.memoize(
(obj) => {
// 复杂计算
return obj.value * 2;
},
(obj) => obj.id // 使用 id 作为缓存键
);

5. curry - 柯里化

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

// 实际应用:创建可复用的函数
const multiply = _.curry((a, b) => a * b);
const double = multiply(2);
const triple = multiply(3);

double(5); // 10
triple(5); // 15

字符串方法

1. camelCase、snakeCase、kebabCase - 命名转换

_.camelCase('Foo Bar'); // 'fooBar'
_.snakeCase('Foo Bar'); // 'foo_bar'
_.kebabCase('Foo Bar'); // 'foo-bar'
_.startCase('fooBar'); // 'Foo Bar'

// 实际应用:API 字段转换
const apiResponse = {
user_name: 'John',
user_email: 'john@example.com'
};

const camelCaseData = _.mapKeys(apiResponse, (value, key) => _.camelCase(key));
// { userName: 'John', userEmail: 'john@example.com' }

2. truncate - 截断字符串

_.truncate('This is a long text', { length: 15 });
// 'This is a lo...'

_.truncate('This is a long text', {
length: 15,
separator: ' '
});
// 'This is a...'

// 实际应用:文章摘要
const article = '这是一篇很长的文章内容...';
const summary = _.truncate(article, { length: 100, omission: '...[阅读更多]' });

3. escape & unescape - HTML 转义

_.escape('<div>Hello & goodbye</div>');
// '&lt;div&gt;Hello &amp; goodbye&lt;/div&gt;'

_.unescape('&lt;div&gt;Hello &amp; goodbye&lt;/div&gt;');
// '<div>Hello & goodbye</div>'

实用工具方法

1. isEmpty - 检查是否为空

_.isEmpty(null); // true
_.isEmpty({}); // true
_.isEmpty([]); // true
_.isEmpty(''); // true
_.isEmpty([1, 2, 3]); // false

// 实际应用:表单验证
const formData = {};
if (_.isEmpty(formData)) {
alert('请填写表单');
}

2. isEqual - 深度比较

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

obj1 === obj2; // false
_.isEqual(obj1, obj2); // true

// 实际应用:检测数据变化
const hasChanged = !_.isEqual(oldData, newData);

3. random - 生成随机数

_.random(0, 5); // 0 到 5 之间的整数
_.random(5); // 0 到 5 之间的整数
_.random(1.2, 5.8, true); // 1.2 到 5.8 之间的浮点数

// 实际应用:随机选择
const colors = ['red', 'blue', 'green', 'yellow'];
const randomColor = colors[_.random(0, colors.length - 1)];

4. times - 重复执行

_.times(3, () => console.log('Hi'));
// 输出三次 'Hi'

_.times(5, (i) => i * 2);
// [0, 2, 4, 6, 8]

// 实际应用:生成测试数据
const users = _.times(10, (i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`
}));

5. range - 生成数字序列

_.range(4); // [0, 1, 2, 3]
_.range(1, 5); // [1, 2, 3, 4]
_.range(0, 20, 5); // [0, 5, 10, 15]

// 实际应用:分页
const totalPages = 10;
const pageNumbers = _.range(1, totalPages + 1);

链式调用

Lodash 支持优雅的链式调用,提高代码可读性。

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

// 使用链式调用
const result = _(users)
.filter({ active: true })
.map('name')
.sortBy()
.value();
// 结果: ['Alice', 'Charlie', 'David']

// 复杂的数据处理
const summary = _(users)
.groupBy('age')
.mapValues(group => ({
count: group.length,
names: _.map(group, 'name')
}))
.value();

最佳实践

1. 按需引入减少包体积

// ❌ 不推荐:引入整个库
import _ from 'lodash';

// ✅ 推荐:按需引入
import debounce from 'lodash/debounce';
import get from 'lodash/get';

2. 使用 lodash/fp 实现函数式编程

import fp from 'lodash/fp';

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

// 函数式风格
const getNames = fp.flow(
fp.filter(user => user.age > 20),
fp.map('name'),
fp.sortBy(fp.identity)
);

getNames(users); // ['Alice', 'Bob']

3. 性能优化技巧

// 使用 memoize 缓存昂贵的计算
const expensiveFunction = _.memoize((input) => {
// 复杂计算
return result;
});

// 使用 debounce/throttle 优化事件处理
const handleInput = _.debounce((value) => {
// 处理输入
}, 300);

// 使用 once 确保初始化只执行一次
const init = _.once(() => {
// 初始化代码
});

4. 类型安全(TypeScript)

import { get, map } from 'lodash';

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

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

// TypeScript 会提供类型推断和检查
const names: string[] = map(users, 'name');
const age: number | undefined = get(users[0], 'age');

5. 常见陷阱与注意事项

// ❌ 错误:直接修改原对象
const original = { a: 1 };
const modified = _.set(original, 'b', 2); // original 也被修改了

// ✅ 正确:先克隆再修改
const modified = _.set(_.cloneDeep(original), 'b', 2);

// ❌ 错误:忘记调用 .value()
const result = _([1, 2, 3]).map(x => x * 2); // 返回 lodash wrapper

// ✅ 正确:使用 .value() 获取结果
const result = _([1, 2, 3]).map(x => x * 2).value(); // [2, 4, 6]

总结

Lodash 是一个功能强大的工具库,掌握它可以显著提高开发效率。本指南涵盖了最常用的方法和实际应用场景,建议在实际项目中逐步应用这些技巧。

学习建议

  1. 从最常用的方法开始(如 get、map、filter、debounce)
  2. 在实际项目中逐步替换原生实现
  3. 关注性能,合理使用缓存和优化函数
  4. 阅读官方文档了解更多高级用法
  5. 结合 TypeScript 获得更好的类型安全

资源链接