Lodash 完整教学指南
目录
简介
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>');
// '<div>Hello & goodbye</div>'
_.unescape('<div>Hello & goodbye</div>');
// '<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 是一个功能强大的工具库,掌握它可以显著提高开发效率。本指南涵盖了最常用的方法和实际应用场景,建议在实际项目中逐步应用这些技巧。
学习建议
- 从最常用的方法开始(如 get、map、filter、debounce)
- 在实际项目中逐步替换原生实现
- 关注性能,合理使用缓存和优化函数
- 阅读官方文档了解更多高级用法
- 结合 TypeScript 获得更好的类型安全
资源链接
- Lodash 官方文档
- Lodash GitHub
- You Don't Need Lodash - 学习原生替代方案