JavaScript 的演变:从 Lodash 和 Underscore 到 vanilla
**由 Kapeel Kokane✏️撰写**
想象一下这个场景——你正计划开始一个新项目。无论它是一个前端项目还是一个后端仓库,你要做的第一件事是什么?
那么,首先你要寻找一个入门套件或样板,它们可以帮助你快速入门,而不是做所有繁重的工作。在我们的例子中,假设你正在启动一个新的前端项目。
在这种情况下,您可以使用 Next.js,因为现在谁还会使用 create-react-app 呢?甚至连官方的 React 文档都不推荐它。接下来呢?您立即安装 Tailwind CSS,因为谁愿意从头开始编写 CSS?然后是状态管理。您可以使用 Redux、MobX、Recoil,甚至是新手 Zustand。我也会做同样的事情。
但是我们接下来要做什么呢?我们通常会安装一个实用程序包来帮助我们完成一些常见任务。谁想编写自己的映射、排序函数、防抖实用程序,甚至深度克隆方法?
但是我们应该使用哪个实用程序包呢?有很多选择:Lodash、Underscore、Ramda 等。最受欢迎的两个是 Lodash 和 Underscore,这也是我们今天要探索的。我们将比较它们的功能,并探讨这些包现在是否有必要。
比较 Lodash 和 Underscore
Underscore 由 Jeremy Ashkenas(Backbone.js 的创建者)于 2009 年创建,旨在提供一组 JavaScript 当时所缺乏的实用函数。它也是为与 Backbone.js 配合使用的,但它逐渐成为需要实用函数的开发人员的最爱,这些开发人员只需调用这些实用函数即可完成工作,而不必担心内部实现和浏览器兼容性。
Lodash 由 John-David Dalton 于 2012 年创建,是 Underscore 的一个分支。它旨在提供更一致的 API 和更好的性能。它还提供了一些不属于 Underscore 的附加实用程序。
捆绑包大小
如果我们比较这两个软件包的 npm 存储库,我们可以看到 Lodash 的包大小(1.41 MB)比 Underscore(906kB)更大。这意味着,当在 npm 项目中使用时,Lodash 在安装节点模块时会占用额外的 500 千字节网络带宽。但是,有了额外的大小,我们获得了一些额外的功能。接下来让我们谈谈它们。
功能
与 Underscore 相比,Lodash 提供了一些附加功能,包括:
除此之外,Lodash 还提供了一些额外的字符串实用程序,例如 `[_.kebabCase](https://lodash.com/docs/4.17.15#kebabCase)` 和 `[_.camelCase](https://lodash.com/docs/4.17.15#camelCase)`,它们可以将任何提供的字符串转换为特定的大小写样式。还有一个 `[_.capitalize](https://lodash.com/docs/4.17.15#capitalize)` 方法可以将任何字符串的首字母大写。
以下是使用这些实用程序的几个示例,这些实用程序目前无法通过 Underscore 实现:
// _.clone example const user = { name: 'John Doe', age: 30, email: 'test@gmail.com', address: { city: 'New York', country: { code: 'US', name: 'United States' } } }; const clonedUser = _.clone(user); // _.set example const user = { name: 'John Doe', age: 30, }; _.set(user, 'address.city', 'New York'); _.set(user, 'address.country.code', 'US'); _.kebabCase('Hello World'); // hello-world _.camelCase('Hello World'); // helloWorld _.capitalize('hello world'); // Hello world
当我们研究常用实用方法的 vanilla JS 替代方案时,我们将在文章后面查看合并示例。
受欢迎程度
如果我们看一下 npm 下载量,Lodash 的下载量为每周 7030 万次,而 Underscore 的下载量则少得多,为每周 1450 万次。这是有道理的,因为一旦 Lodash 可用,人们就会选择“较新”版本的实用程序包,而不是旧版本,旧版本会自动变得“过时”。
我们真的需要实用程序包吗?
现在,让我们看看这些库最初创建的主要用例——实用函数。过去,JavaScript 规范缺少一些基本功能。即使它确实通过将它们添加到规范中引入了新功能,但并非所有浏览器都实现了它们。让我们看看其中的一些。
根据条件过滤数组
ES5 中引入了 `Array.prototype.filter` 方法。它创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。但是,它直到 2015 年才在所有主流浏览器中普遍可用。在此之前,如果您想编写函数代码来过滤数组,则必须使用 Underscore 或 Lodash 中的实用方法,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const evenNumbers = _.filter(numbers, (number) => number % 2 === 0);
但在 ES2015 之后,它可以轻松地用原始 JavaScript 实现,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const evenNumbers = numbers.filter((number) => number % 2 === 0);
将数组精简为单个结果
`Array.prototype.reduce` 方法也出现了同样的情况。在 2015 年之前,如果你想编写函数式代码来减少数组,你必须使用 Underscore 或 Lodash 中的实用方法,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const sum = _.reduce(numbers, (acc, number) => acc + number, 0);
在ES2015之后,它可以很容易地用原生JavaScript实现:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const sum = numbers.reduce((acc, number) => acc + number, 0);
对每个数组元素执行操作
现在让我们看一下 `Array.prototype.forEach` 方法。如果我们想对数组的每个元素执行操作,我们必须使用 `for` 循环或 Underscore 或 Lodash 中的实用方法,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; _.forEach(numbers, (number) => console.log(number));
但是在 ES2015 之后,它可以用原始 JavaScript 实现,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; numbers.forEach((number) => console.log(number));
检查变量是否为数组
如果我们想检查一个变量是否是一个数组,我们必须使用 Underscore 或 Lodash 的实用方法,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const isNumbersArray = _.isArray(numbers);
使用 ES5,我们可以轻松地用原始 JavaScript 实现它,如下所示:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const isNumbersArray = Array.isArray(numbers);
从对象中选取属性
现在让我们看一些对象操作。如果我们想从对象中挑选一些键,我们必须使用 Underscore 或 Lodash 中的实用方法,如下所示:
const user = { name: 'John Doe', age: 30, email: ' }; const pickedUser = _.pick(user, ['name', 'age']);
这将使用用户对象的键创建一个新对象,在本例中,键是“name”和“age”。但是,从 ES5 开始,我们可以轻松地在原生 JavaScript 中实现它,如下所示:
const user = { name: 'John Doe', age: 30, email: 'test@gmail.com' }; const pickedUser = Object.fromEntries(Object.entries(user).filter(([key]) => ['name', 'age'].includes(key)));
合并两个对象
如果我们想要合并两个对象,以便新对象具有来自两个对象的键的超集,我们必须使用 Lodash 中的实用方法,如下所示:
const user = { name: 'John Doe', age: 30, email: 'test@gmail.com' } const newUser = { age: 31 } const mergedUser = _.merge({}, user, newUser);
请注意,Underscore 没有此功能,但在 ES5 中,我们可以使用扩展运算符在原始 JavaScript 中轻松实现它,如下所示:
const mergedUser = {...user, ...newUser};
我们还可以使用 `[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)` 方法,如下所示:
const mergedUser = Object.assign({}, user, newUser);
深度克隆对象
如果我们想要深度克隆一个对象,我们必须使用 Underscore 或 Lodash 的实用方法,如下所示:
const user = { name: 'John Doe', age: 30, email: 'test@gmail.com' }; const clonedUser = _.cloneDeep(user);
但是,现代 JavaScript 具有诸如“parse”和“stringify”之类的 JSON 实用程序,可用于深度克隆对象,如下所示:
const clonedUser = JSON.parse(JSON.stringify(user));
现在,让我们讨论一些更复杂的用例。
限制函数调用
如果我们想要限制一个函数,即将函数执行次数限制为给定时间范围内一次,我们必须使用 Underscore 或 Lodash 中的实用方法,如下所示:
const throttledFunction = _.throttle(() => console.log('Throttled function'), 1000);
在这种情况下,该函数每秒只会执行一次,即 1000 毫秒。
也可以用原生 JavaScript 实现相同的功能,如下所示:
function throttle(func, delay) { let lastTime = 0; return function() { const now = Date.now(); if (now - lastTime >= delay) { lastTime = now; func.apply(this, arguments); } }; } const throttledFunction = throttle(() => console.log('Throttled function'), 1000);
函数调用防抖
如果我们想要对一个函数进行去抖动,即自上次函数执行后在特定时间内停止该函数的执行,我们必须使用 Underscore 或 Lodash 中的实用方法,如下所示:
const debouncedFunction = _.debounce(() => console.log('Debounced function'), 1000);
但同样的功能也可以用原生 JavaScript 实现,如下所示:
function debounce(func, delay) { let timeoutId; return function() { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, arguments); }, delay); }; } const debouncedFunction = debounce(() => console.log('Debounced function'), 1000);
结论
那么,我们真的需要像 Lodash 或 Underscore 这样的实用程序库吗?到 2024 年也不需要了。
随着 ES2015 及更高版本的 JavaScript 的推出,该语言取得了长足的进步。它引入了许多可用于执行常见任务的实用方法。即使您需要一些额外的功能,您也可以使用该语言的最新功能在原生 JavaScript 中轻松实现它们,就像我们在本文中对扩展运算符所做的那样。
因此,下次您在设置新项目时发现自己需要使用 Lodash 或 Underscore 时,请三思。节省一些网络带宽(来自 npm 模块)并编写自己的实用函数。这不仅可以帮助您更好地理解语言,而且还可以让您成为更好的开发人员。
您是否添加新的 JS 库来构建新功能或提高性能?如果他们做相反的事情怎么办?
毫无疑问,前端变得越来越复杂。当您向应用添加新的 JavaScript 库和其他依赖项时,您需要更高的可见性,以确保您的用户不会遇到未知问题。
LogRocket 是一个前端应用程序监控解决方案,可以让您重播 JavaScript 错误,就像它们发生在您自己的浏览器中一样,以便您可以更有效地对错误做出反应。

LogRocket 可与任何应用程序完美配合,无论使用哪种框架,并且具有可记录来自 Redux、Vuex 和 @ngrx/store 的其他上下文的插件。您无需猜测问题发生的原因,而是可以汇总和报告问题发生时应用程序的状态。LogRocket 还可以监控应用程序的性能,报告客户端 CPU 负载、客户端内存使用情况等指标。
自信构建 — 开始免费监控。