JavaScript 开发人员的 10 个重要 Lodash 函数
已发表: 2021-10-05对于 JavaScript 开发人员,Lodash 无需介绍。 然而,图书馆很大,常常让人感到不知所措。 不再!
洛达什,洛达什,洛达什。 . . 我从哪里开始!
曾经有一段时间 JavaScript 生态系统还处于萌芽状态。 如果愿意的话,它可以与狂野的西部或丛林进行比较,那里发生了很多事情,但是对于日常开发人员的挫折和生产力,几乎没有答案。
然后洛达什进入了现场,感觉就像洪水淹没了一切。 从简单的日常需求(如排序)到复杂的数据结构转换,Lodash 加载了(甚至超载!)功能,将 JS 开发人员的生活变成了纯粹的幸福。

Lodash今天在哪里? 好吧,它仍然拥有它最初提供的所有好处,然后是一些,但它似乎已经失去了在 JavaScript 社区中的份额。 为什么? 我能想到几个原因:
- Lodash 库中的一些函数在应用于大型列表时(并且仍然)很慢。 虽然这永远不会影响 95% 的项目,但剩余 5% 的有影响力的开发人员给了 Lodash 一个坏消息,并且影响层层递进到基层。
- 在 JS 生态系统中有一种趋势(甚至可能对 Golang 人说同样的话),狂妄自大比必要的更为普遍。 所以,依赖像 Lodash 这样的东西被认为是愚蠢的,当人们提出这样的解决方案时,它会在 StackOverflow 等论坛上遭到抨击(“什么?!使用整个库来做这样的事情?我可以结合
filter()和reduce()来实现在一个简单的函数中做同样的事情!”)。 - 洛达什老了。 至少按照 JS 标准。 它于 2012 年问世,截至撰写本文时,已经快十年了。 API一直很稳定,每年都没有太多令人兴奋的东西(只是因为没有必要),这让普通的过度兴奋的JS开发人员感到无聊。
在我看来,不使用 Lodash 是我们 JavaScript 代码库的重大损失。 它已经证明了我们在工作中遇到的日常问题的无错误和优雅的解决方案,并且使用它只会使我们的代码更具可读性和可维护性。
话虽如此,让我们深入了解一些常见的(或不常见的!)Lodash 函数,看看这个库是多么的有用和美丽。
克隆。 . . 深!
由于 JavaScript 中的对象是通过引用传递的,因此当希望克隆某些内容并希望新数据集不同时,这会让开发人员头疼。
let people = [ { name: 'Arnold', specialization: 'C++', }, { name: 'Phil', specialization: 'Python', }, { name: 'Percy', specialization: 'JS', }, ]; // Find people writing in C++ let folksDoingCpp = people.filter((person) => person.specialization == 'C++'); // Convert them to JS! for (person of folksDoingCpp) { person.specialization = 'JS'; } console.log(folksDoingCpp); // [ { name: 'Arnold', specialization: 'JS' } ] console.log(people); /* [ { name: 'Arnold', specialization: 'JS' }, { name: 'Phil', specialization: 'Python' }, { name: 'Percy', specialization: 'JS' } ] */ 请注意,在我们纯真无邪的情况下,尽管我们是善意的,原始的people数组在此过程中发生了变异(Arnold 的专业化从C++变为JS )——对底层软件系统的完整性造成重大打击! 事实上,我们需要一种方法来制作原始数组的真实(深度)副本。

你也许会争辩说这是一种“愚蠢”的 JS 编码方式; 然而,实际情况有点复杂。 是的,我们有可爱的解构运算符可用,但是任何尝试解构复杂对象和数组的人都知道这种痛苦。 然后,有使用序列化和反序列化(可能是 JSON)来实现深度复制的想法,但这只会使您的代码对读者来说更加混乱。
相比之下,当 Lodash 被使用时,看看这个解决方案是多么优雅和简洁:
const _ = require('lodash'); let people = [ { name: 'Arnold', specialization: 'C++', }, { name: 'Phil', specialization: 'Python', }, { name: 'Percy', specialization: 'JS', }, ]; let peopleCopy = _.cloneDeep(people); // Find people writing in C++ let folksDoingCpp = peopleCopy.filter( (person) => person.specialization == 'C++' ); // Convert them to JS! for (person of folksDoingCpp) { person.specialization = 'JS'; } console.log(folksDoingCpp); // [ { name: 'Arnold', specialization: 'JS' } ] console.log(people); /* [ { name: 'Arnold', specialization: 'C++' }, { name: 'Phil', specialization: 'Python' }, { name: 'Percy', specialization: 'JS' } ] */ 注意people数组在深度克隆后是如何保持不变的(在这种情况下,Arnold 仍然专注于C++ )。 但更重要的是,代码简单易懂。
从数组中删除重复项
从数组中删除重复项听起来像是一个很好的面试/白板问题(请记住,如有疑问,请在问题上使用哈希图!)。 而且,当然,您始终可以编写自定义函数来执行此操作,但是如果遇到几种不同的场景来使您的数组独一无二,该怎么办? 您可以为此编写其他几个函数(并冒着遇到细微错误的风险),或者您可以只使用 Lodash!

我们的第一个独特数组示例相当简单,但它仍然代表了 Lodash 带来的速度和可靠性。 想象一下通过自己编写所有自定义逻辑来做到这一点!
const _ = require('lodash'); const userIds = [12, 13, 14, 12, 5, 34, 11, 12]; const uniqueUserIds = _.uniq(userIds); console.log(uniqueUserIds); // [ 12, 13, 14, 5, 34, 11 ]请注意,最终的数组未排序,当然,这在这里无关紧要。 但是现在,让我们想象一个更复杂的场景:我们从某个地方拉取了一组用户,但我们希望确保它只包含唯一用户。 轻松使用 Lodash!
const _ = require('lodash'); const users = [ { id: 10, name: 'Phil', age: 32 }, { id: 8, name: 'Jason', age: 44 }, { id: 11, name: 'Rye', age: 28 }, { id: 10, name: 'Phil', age: 32 }, ]; const uniqueUsers = _.uniqBy(users, 'id'); console.log(uniqueUsers); /* [ { id: 10, name: 'Phil', age: 32 }, { id: 8, name: 'Jason', age: 44 }, { id: 11, name: 'Rye', age: 28 } ] */ 在这个例子中,我们使用uniqBy()方法告诉 Lodash 我们希望对象在id属性上是唯一的。 在一行中,我们表达了可能需要 10-20 行的内容,并引入了更多的错误范围!
关于在 Lodash 中使事物变得独特,还有更多可用的东西,我鼓励您查看文档。
两个数组的差
联合、差异等,听起来像是最好留在高中关于集合论的枯燥讲座中的术语,但它们在日常实践中经常出现。 拥有一个列表并希望将另一个列表与其合并或想要找到与另一个列表相比哪些元素是唯一的,这是很常见的; 对于这些场景,差分函数是完美的。

让我们通过一个简单的场景开始不同的旅程:您已经收到系统中所有用户 ID 的列表,以及帐户处于活动状态的用户列表。 你如何找到不活动的ID? 很简单吧?
const _ = require('lodash'); const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8]; const activeUserIds = [1, 4, 22, 11, 8]; const inactiveUserIds = _.difference(allUserIds, activeUserIds); console.log(inactiveUserIds); // [ 3, 2, 10 ] 如果在更现实的环境中发生这种情况,您必须使用对象数组而不是简单的基元呢? 好吧,Lodash 有一个很好的differenceBy()方法!
const allUsers = [ { id: 1, name: 'Phil' }, { id: 2, name: 'John' }, { id: 3, name: 'Rogg' }, ]; const activeUsers = [ { id: 1, name: 'Phil' }, { id: 2, name: 'John' }, ]; const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id'); console.log(inactiveUsers); // [ { id: 3, name: 'Rogg' } ]整洁,对吧?!
和差异一样,Lodash 中还有其他的方法来处理常见的集合操作:并集、交集等。
展平阵列
扁平化数组的需要经常出现。 一个用例是您收到了一个 API 响应,需要在复杂的嵌套对象/数组列表上应用一些map()和filter()组合来提取用户 ID,现在您只剩下数组的数组。 这是描述这种情况的代码片段:
const orderData = { internal: [ { userId: 1, date: '2021-09-09', amount: 230.0, type: 'prepaid' }, { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' }, ], external: [ { userId: 3, date: '2021-08-08', amount: 30.0, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' }, ], }; // find user ids that placed postpaid orders (internal or external) const postpaidUserIds = []; for (const [orderType, orders] of Object.entries(orderData)) { postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid')); } console.log(postpaidUserIds); 你能猜出postPaidUserIds现在是什么样子吗? 提示:太恶心了!
[ [], [ { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' } ] ] 现在,如果您是一个明智的人,您不想编写自定义逻辑来提取订单对象并将它们整齐地排列在数组中的一行中。 只需使用flatten()方法并享受葡萄:
const flatUserIds = _.flatten(postpaidUserIds); console.log(flatUserIds); /* [ { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' }, { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' } ] */ 请注意flatten()只深入一层。 也就是说,如果您的对象卡在两层、三层或更多层深, flatten()它们会让您失望。 在这些情况下,Lodash 有flattenDeep()方法,但请注意,在非常大的结构上应用此方法会减慢速度(因为在幕后,有一个递归操作在起作用)。

对象/数组是否为空?
由于“假”值和类型在 JavaScript 中的工作方式,有时像检查空性这样简单的事情会导致存在恐惧。

如何检查数组是否为空? 您可以检查其length是否为0 。 现在,您如何检查对象是否为空? 嗯……等一下! 这就是那种不安的感觉开始的地方,那些包含[] == false和{} == false类内容的 JavaScript 示例开始在我们脑海中盘旋。 当面临交付功能的压力时,像这样的地雷是您最不需要的东西——它们会使您的代码难以理解,并且会给您的测试套件带来不确定性。
处理缺失数据
在现实世界中,数据听我们的; 无论我们多么想要它,它都很少精简和理智。 一个典型的例子是在作为 API 响应接收的大型数据结构中缺少空对象/数组。

假设我们收到以下对象作为 API 响应:
const apiResponse = { id: 33467, paymentRefernce: 'AEE3356T68', // `order` object missing processedAt: `2021-10-10 00:00:00`, }; 如图所示,我们通常会在 API 的响应中获得一个order对象,但情况并非总是如此。 那么,如果我们有一些依赖于这个对象的代码呢? 一种方法是防御性编码,但根据order对象的嵌套方式,如果我们希望避免运行时错误,我们很快就会编写非常难看的代码:
if ( apiResponse.order && apiResponse.order.payee && apiResponse.order.payee.address ) { console.log( 'The order was sent to the zip code: ' + apiResponse.order.payee.address.zipCode ); }是的,写起来很丑,读起来很丑,维护起来也很丑,等等。 值得庆幸的是,Lodash 有一种直接的方法来处理这种情况。
const zipCode = _.get(apiResponse, 'order.payee.address.zipCode'); console.log('The order was sent to the zip code: ' + zipCode); // The order was sent to the zip code: undefined 还有一个很棒的选择是提供默认值而不是为丢失的东西设置undefined :
const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA'); console.log('The order was sent to the zip code: ' + zipCode2); // The order was sent to the zip code: NA 我不了解你,但get()是让我眼中流泪的事情之一。 这不是什么华而不实的。 没有需要记住的参考语法或选项,但看看它可以减轻多少集体痛苦!
去抖动
如果您不熟悉,去抖动是前端开发中的一个常见主题。 这个想法是有时不是立即启动一个动作而是在一段时间后(通常是几毫秒)启动一个动作是有益的。 这意味着什么? 这是一个例子。

想象一个带有搜索栏的电子商务网站(好吧,现在任何网站/网络应用程序!)。 为了获得更好的 UX,我们不希望用户必须按 Enter(或更糟的是,按“搜索”按钮)才能根据他们的搜索词显示建议/预览。 但显而易见的答案是有点加载:如果我们为搜索栏的onChange()添加一个事件侦听器,并为每次击键触发 API 调用,我们就会为我们的后端创造一个噩梦; 会有太多不必要的调用(例如,如果搜索“白色地毯刷”,总共有18个请求!)并且几乎所有这些都无关紧要,因为用户输入还没有完成。
答案在于去抖动,其想法是:不要在文本更改后立即发送 API 调用。 等待一段时间(例如 200 毫秒),如果到那时还有另一个击键,请取消较早的时间计数并再次开始等待。 因此,只有当用户暂停时(或者因为他们正在思考,或者因为他们已经完成并且他们期望得到一些响应),我们才会向后端发送 API 请求。
我描述的整体策略很复杂,我不会深入研究定时器管理和取消的同步; 但是,如果您使用的是 Lodash,实际的去抖动过程非常简单。
const _ = require('lodash'); const axios = require('axios'); // This is a real dogs' API, by the way! const fetchDogBreeds = () => axios .get('https://dog.ceo/api/breeds/list/all') .then((res) => console.log(res.data)); const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // after one second debouncedFetchDogBreeds(); // shows data after some time 如果你在想setTimeout()我会做同样的工作,嗯,还有更多! Lodash 的 debounce 具有许多强大的功能; 例如,您可能希望确保去抖动不是无限期的。 也就是说,即使每次函数即将触发(从而取消整个过程)时都有一个击键,您可能希望确保 API 调用在两秒钟后仍然进行。 为此,Lodash debounce debounce()有maxWait选项:
const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway请查看官方文档以进行更深入的了解。 它们充满了非常重要的东西!
从数组中删除值
我不了解您,但我讨厌编写用于从数组中删除项目的代码。 首先,我必须获取项目的索引,检查索引是否实际有效,如果有效,则调用splice()方法,等等。 我永远记不住语法,因此需要一直查资料,最后,我有一种唠叨的感觉,我让一些愚蠢的错误潜入。

const greetings = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pull(greetings, 'wave', 'hi'); console.log(greetings); // [ 'hello', 'hey' ]请注意两点:
- 原始数组在此过程中发生了变化。
-
pull()方法删除所有实例,即使有重复。
还有另一个称为pullAll()相关方法,它接受一个数组作为第二个参数,从而可以更轻松地一次删除多个项目。 当然,我们可以将pull()与扩展运算符一起使用,但请记住,Lodash 出现的时候,扩展运算符甚至还不是该语言的提案!
const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi']; _.pullAll(greetings2, ['wave', 'hi']); console.log(greetings2); // [ 'hello', 'hey' ]元素的最后一个索引
JavsScript 的原生indexOf()方法很酷,除非您有兴趣从相反方向扫描数组! 再一次,是的,您可以编写一个递减循环并找到元素,但为什么不使用更优雅的技术呢?

这是一个使用lastIndexOf()方法的快速 Lodash 解决方案:
const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7]; const index = _.lastIndexOf(integers, -1); console.log(index); // 7不幸的是,这种方法没有变体,我们可以在其中查找复杂的对象,甚至可以传递自定义查找函数。
压缩。 解压!

除非您曾使用过 Python,否则 zip/unzip 是一种您在作为 JavaScript 开发人员的整个职业生涯中可能永远不会注意到或想象的实用程序。 也许有一个很好的理由:很少像filter()等那样迫切需要 zip/unzip。但是,它是最好的鲜为人知的实用程序之一,可以帮助您在某些情况下创建简洁的代码.
与听起来相反,zip/unzip 与压缩无关。 相反,它是一个分组操作,其中相同长度的数组可以转换为单个数组数组,其中相同位置的元素打包在一起 ( zip() ) 并返回 ( unzip() )。 是的,我知道,试图用文字凑合会变得模糊,所以让我们看一些代码:
const animals = ['duck', 'sheep']; const sizes = ['small', 'large']; const weight = ['less', 'more']; const groupedAnimals = _.zip(animals, sizes, weight); console.log(groupedAnimals); // [ [ 'duck', 'small', 'less' ], [ 'sheep', 'large', 'more' ] ] 原来的三个数组被转换成一个只有两个数组的数组。 这些新数组中的每一个都代表一个动物,所有动物都集中在一个地方。 因此,指数0告诉我们它是哪种动物,指数1告诉我们它的大小,而指数2告诉我们它的重量。 因此,现在可以更轻松地处理数据。 一旦你对数据应用了你需要的任何操作,你可以使用unzip()再次分解它并将它发送回原始源:
const animalData = _.unzip(groupedAnimals); console.log(animalData); // [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]zip/unzip 实用程序不会在一夜之间改变您的生活,但总有一天会改变您的生活!
结论
(我把本文用到的所有源码都放在这里了,大家可以直接在浏览器上试试!)
Lodash 文档充满了会让您大吃一惊的示例和函数。 在 JS 生态系统中受虐狂似乎越来越多的时代,Lodash 就像一股新鲜空气,我强烈建议您在您的项目中使用这个库!
