Underscore.js
JavaScript 经典工具函数库,提供 100+ 个常用数组、对象、函数操作方法的轻量级实用工具库。
Underscore.js 由 Jeremy Ashkenas 于 2009 年创建,是最早的 JavaScript 工具库之一。提供 100+ 个常用工具方法,涵盖数组、对象、函数增强和模板系统。gzip 约 16KB,npm 周下载量约 2100 万+。已进入维护模式,仅修复安全漏洞,但在大量遗留项目和底层框架中仍广泛使用。
Underscore.js
JavaScript 经典工具函数库,提供 100+ 个常用数组、对象、函数操作方法的轻量级实用工具库。
技术简介说明
Underscore.js 由 Jeremy Ashkenas 于 2009 年创建,是最早一批 JavaScript 工具函数库之一。它以 _ 作为命名空间,提供了超过 100 个常用的工具方法,涵盖数组操作(each、map、filter、reduce)、对象操作(keys、values、extend、pick)、函数增强(bind、debounce、throttle、memoize)以及模板系统等,为早期 JavaScript 开发提供了标准化的工具函数集合。
在 ES5/ES6 标准化之前,Underscore.js 几乎是前端开发者的"标配依赖",它与 Backbone.js 形成了经典的搭配组合。即使在现代 JavaScript 原生 API 已经覆盖了大部分常用方法的今天,Underscore.js 依然在大量遗留项目和底层框架中广泛使用,其 npm 周下载量仍保持在 2100 万+ 级别。
Underscore.js 的设计哲学是"实用优先"——不追求函数式的纯粹性,而是以简洁、直观的方式解决日常开发中的常见问题。它的源码结构清晰、可读性强,长期以来也是许多开发者学习 JavaScript 源码的入门读物。
基本信息
- 官网:https://underscorejs.org
- GitHub:https://github.com/jashkenas/underscore
- License:MIT
- 最新版本:1.13.8(2026 年 3 月发布,修复 CVE-2026-27601 安全漏洞)
- 主要维护者/公司:Julian Gonggrijp(当前主要维护者)、Jeremy Ashkenas(原始作者)
- GitHub Stars:~27.3k
- npm 周下载量:~2100-2300 万
- Snyk 健康评分:87/100(关键生态项目)
快速上手
安装
# npm
npm install underscore
# yarn
yarn add underscore
# pnpm
pnpm add underscore
# CDN(浏览器直接使用)
<script src="https://cdn.jsdelivr.net/npm/[email protected]/underscore-min.js"></script>
# ESM 模块引入
<script type="module">
import _ from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';
</script>基础配置
// CommonJS 引入
const _ = require('underscore');
// ES Module 引入
import _ from 'underscore';
// 浏览器全局变量引入(UMD)
// 引入 script 标签后,全局 _ 即可使用自定义模板设置:
// 自定义模板分隔符
_.templateSettings = {
evaluate: /\{\{([\s\S]+?)\}\}/g,
interpolate: /\{\{=([\s\S]+?)\}\}/g,
escape: /\{\{-([\s\S]+?)\}\}/g
};最小示例
import _ from 'underscore';
// 数组操作
const list = [1, 2, 3, 4, 5];
const doubled = _.map(list, n => n * 2); // [2, 4, 6, 8, 10]
const evens = _.filter(list, n => n % 2 === 0); // [2, 4]
const sum = _.reduce(list, (acc, n) => acc + n, 0); // 15
// 对象操作
const user = { name: 'Alice', age: 28, role: 'admin' };
const keys = _.keys(user); // ['name', 'age', 'role']
const picked = _.pick(user, 'name', 'age'); // { name: 'Alice', age: 28 }
// 函数增强
const expensiveCalc = _.memoize((n) => {
// 复杂计算...
return n * n;
});
// 链式调用
const result = _.chain([1, 2, 3, 4, 5])
.filter(n => n > 2)
.map(n => n * 10)
.value(); // [30, 40, 50]核心概念与架构
命名空间设计
Underscore.js 使用 _ 作为全局命名空间,所有工具方法都挂载在 _ 对象上。这种设计避免了全局命名冲突,同时提供了简洁的调用语法。
三大核心模块
-
集合操作(Collection):
each、map、reduce、filter、find、where、sortBy、groupBy等,对数组和对象进行统一迭代处理。 -
数组方法(Array):
first、last、flatten、compact、difference、uniq、zip、object等,专注于数组的数据转换。 -
函数工具(Function):
bind、debounce、throttle、memoize、delay、defer、once、after、partial、compose等,增强函数的行为。
链式调用机制
通过 _.chain() 方法开启链式调用,每次调用返回一个包装对象,最终通过 .value() 获取结果。内部通过 _.mixin() 将所有方法注入到链式调用体系中。
模板引擎
内置轻量级模板引擎 _.template(),支持变量插值、表达式求值和 HTML 转义,可在服务端和客户端使用。
核心特性
- 轻量级体积:gzip 后约 16KB,远小于 Lodash(~33KB),适合对包体积敏感的场景。
- 100+ 工具方法:覆盖数组、对象、函数、集合、模板等常用操作场景。
- 链式调用:
_.chain()支持流畅的数据管道操作,提升代码可读性。 - 模板引擎:内置
_.template()模板系统,支持自定义分隔符。 - 函数增强:
debounce、throttle、memoize、once等经典函数工具。 - 深度比较:
_.isEqual()支持深度对象比较(注意 CVE-2026-27601 相关的栈溢出问题)。 - 跨环境兼容:支持浏览器、Node.js、Web Worker 等多种运行环境。
- ESM 支持:提供 ES Module 格式导出,支持现代构建工具。
- 无外部依赖:零依赖,无需额外安装包。
- 可读源码:注释丰富,结构清晰,是学习 JavaScript 设计模式的优质源码。
生态图
核心依赖
Underscore.js 本身零外部依赖,是一个完全自包含的工具库。
生态插件
- underscore.string:字符串处理扩展库,补充
_.str命名空间下的字符串操作方法。 - underscore-contrib:社区贡献的扩展方法集合,包含函数式编程、对象操作等额外工具。
- Backbone.js:经典 MVC 框架,底层依赖 Underscore.js,两者历史上紧密绑定。
相关项目
- Lodash:由 Underscore.js fork 而来,功能更丰富、性能更优的现代替代品。
- @types/underscore:TypeScript 类型定义包,提供完整的类型支持。
- underscore-template:独立的模板引擎提取版本。
社区生态
Underscore.js 的社区生态相比 Lodash 已经显著萎缩。在 Stack Overflow 上有超过 10 万个相关问题,但新提问量逐年减少。npm 的大量下载主要来自存量项目(Drupal、Meteor 等框架的传递依赖)。
适用场景
- 遗留项目维护:大量 2010-2018 年构建的项目依赖 Underscore.js,维护这些项目时需要熟悉其 API。
- 轻量级工具需求:当只需要少量工具方法且对包体积有严格要求时,16KB 的体积优势明显。
- 服务端模板渲染:
_.template()提供简单可靠的轻量级模板功能,适合服务端渲染场景。 - 函数工具方法:
debounce、throttle、memoize等函数增强工具在各类项目中都有广泛应用。 - 数据转换管道:链式调用
_.chain()适合处理多步骤的数据转换和过滤逻辑。 - 学习与参考:源码结构清晰、注释丰富,是学习 JavaScript 工具库设计的优质参考。
开发与工程化
构建流程
# 克隆仓库
git clone https://github.com/jashkenas/underscore.git
cd underscore
# 安装依赖
npm install
# 运行测试
npm test
# 构建(生成 ESM、CJS、UMD 格式)
npm run build源码结构
underscore/
├── modules/ # 各方法的独立 ES Module 源码
│ ├── index.js # 入口文件
│ ├── map.js
│ ├── filter.js
│ └── ...
├── underscore.js # UMD 打包入口
├── package.json
└── test/ # 测试用例
工程化最佳实践
- 按需引入:使用 ES Module 的具名导入,配合构建工具的 tree-shaking 减少打包体积。
import { map, filter, reduce } from 'underscore'; - 版本锁定:在
package.json中使用精确版本号,避免意外升级引入的兼容性问题。 - 安全更新:关注 Snyk 的安全公告,及时升级到修复版本。
CI/CD 集成
Underscore.js 作为纯 JavaScript 库,集成无特殊要求。注意在 CI 流水线中配置安全扫描(如 Snyk、Socket.dev),及时检测已知漏洞。
性能与安全
性能特点
- 整体性能略逊于 Lodash(Lodash 在多数基准测试中快 20-30%)。
_.each()、_.map()等迭代方法在 V8 引擎上表现良好,但不及原生Array.prototype.map()等方法。_.flatten()和_.isEqual()存在已知性能问题(CVE-2026-27601,深度嵌套数据可能导致栈溢出)。
安全注意事项
| 漏洞编号 | 影响版本 | 描述 | 修复版本 |
|---|---|---|---|
| CVE-2026-27601 | ≤ 1.13.7 | _.flatten 和 _.isEqual 中无限制递归导致 DoS | 1.13.8 |
| CVE-2021-23358 | ≤ 1.13.0 | _.template() 中 Arbitrary Code Execution(模板注入) | 1.13.1 |
重要:务必升级到 1.13.8+,确保已修复所有已知安全漏洞。
优化建议
- 对于频繁调用的计算方法,使用
_.memoize()缓存结果。 - 处理大型数据集时,优先使用原生方法或考虑 Lodash 的惰性求值(
_.lazy())。 - 避免对深度嵌套对象使用
_.isEqual()和_.flatten(),改用structuredClone()或自定义深度比较。
技术对比
| 特性 | Underscore.js | Lodash | Ramda | 原生 JS (ES2020+) |
|---|---|---|---|---|
| 包体积 (gzip) | ~16 KB | ~33 KB | ~12 KB(单方法更小) | 0 KB |
| 方法数量 | 100+ | 300+ | 250+ | 取决于 API |
| 性能 | 中等 | 快(高度优化) | 中等(curry 开销) | 最快(原生) |
| 函数式风格 | 实用主义 | 多范式支持 | 纯函数式 | 混合 |
| 自动柯里化 | ❌ | ❌(lodash/fp 支持) | ✅(所有函数) | ❌ |
| 数据参数顺序 | 数据优先 | 数据优先 | 数据最后(利于柯里化) | — |
| 不可变性 | 不强制 | 可选(lodash/fp) | 核心设计原则 | 视 API 而定 |
| Tree-shaking | 部分支持 | 支持 | 支持 | — |
| TypeScript 支持 | @types/underscore | 内置 | @types/ramda | 内置 |
| 维护状态 | 安全补丁维护 | 活跃维护 | 活跃维护(向 1.0 推进) | — |
| npm 周下载量 | ~2300 万 | ~1.2 亿 | ~1350 万 | — |
选型建议:
- 新项目优先使用原生 JavaScript API,只在需要复杂数据操作时引入工具库。
- 需要函数式编程范式 → 选择 Ramda。
- 需要全面的工具方法集合和高性能 → 选择 Lodash。
- 维护遗留项目或需要最小包体积 → 可继续使用 Underscore.js。
最佳实践
生产环境建议
-
优先使用原生方法:
Array.prototype.map、Object.keys、Array.prototype.flat等原生方法在性能上优于工具库方法。只在需要深度比较、复杂模板、函数增强等场景使用 Underscore。 -
按需引入减少体积:
// 好 ✅ — 只引入需要的方法 import { debounce, throttle, memoize } from 'underscore'; // 不好 ❌ — 引入整个库 import _ from 'underscore'; -
锁定安全版本:确保使用 1.13.8+,在 CI 中集成安全扫描。
-
模板安全:使用
_.template()时注意 XSS 防护,对用户输入使用_.escape()转义。 -
避免深层操作性能陷阱:
_.isEqual()和_.flatten()对深度嵌套数据存在性能问题,大对象比较建议使用structuredClone()+ 自定义比较或第三方深度比较库。
常见陷阱
_.template()模板注入:不要将用户可控内容直接传入模板字符串,可能导致远程代码执行(RCE)。_.isEqual()递归溢出:对超深层嵌套对象使用_.isEqual()可能导致栈溢出(已在 1.13.8 修复了 CVE-2026-27601 的部分问题,但仍需注意)。- 命名空间冲突:如果页面中已有
_变量(如 Lodash),使用_.noConflict()恢复原始变量。
技术局限与边界
已知限制
-
功能停滞:Underscore.js 已进入维护模式,不再添加新功能。新特性(如
Array.prototype.at()、Object.hasOwn()等原生 API 的对应封装)不会被加入。 -
性能上限:相比 Lodash 的高度优化实现,Underscore.js 在大数据集处理场景下性能差距明显。
-
缺乏函数式范式:不支持自动柯里化、不可变数据管道等函数式编程特性,不适合需要纯函数式风格的场景。
-
TypeScript 支持薄弱:依赖第三方
@types/underscore,类型定义不如 Lodash(内置类型)或 Ramda 精确。 -
模块生态萎缩:相比 Lodash 的
lodash-es和完善的 tree-shaking 支持,Underscore.js 的模块化体验略逊。
不适用场景
- 全新的现代前端项目(建议优先使用原生 API 或 Lodash)。
- 需要函数式编程范式的项目(建议使用 Ramda)。
- 对性能要求极高的数据处理场景(建议使用 Lodash 或原生方法)。
- 需要 TypeScript 强类型支持的项目(Lodash 或 Ramda 的类型支持更完善)。
迁移注意事项
- 从 Underscore 迁移到 Lodash 时注意 API 差异:部分方法名相同但行为不同(如
_.map的参数顺序)。 - 迁移到原生 JS 时注意兼容性:
_.flatten()→Array.prototype.flat()(ES2019)、_.assign()→Object.assign()(ES2015)。 - 可使用 You Don't Need Lodash/Underscore 项目对照原生替代方案。
学习资源
- 官方文档:https://underscorejs.org(完整 API 参考和注释源码)
- 注释源码阅读:https://underscorejs.org/docs/underscore.html(经典源码注释版)
- 中文文档:https://www.underscore-js.com(社区维护的中文翻译版)
- GitHub 仓库:https://github.com/jashkenas/underscore
- You Don't Need Lodash/Underscore:https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore(原生替代方案对照表)
- Stack Overflow:搜索
[underscore.js]标签(10 万+ 相关问答) - 安全公告:Snyk - underscore
2026 年现状
最新版本
- 当前版本:1.13.8(2026 年 3 月发布),为安全修复版本。
- 上次功能更新:1.13.7(2022 年),已约 4 年未有功能性版本发布。
发展趋势
Underscore.js 已明确进入维护模式(sustainment mode)。项目不再进行功能开发,只针对安全漏洞进行修复。这一状态预计将长期持续。
社区活跃度
- GitHub Issues 和 PR 活跃度很低,社区贡献较少。
- npm 下载量(~2300 万/周)主要来自存量项目和框架的传递依赖(Drupal、Meteor、Backbone.js 等),新项目很少主动选择 Underscore。
- 在 The New Stack、Medium 等平台的 2025/2026 年度回顾文章中,Underscore.js 被列入"应该在 2025 年告别的 JavaScript 库"名单,主要理由是原生 JS 已覆盖其大部分功能。
未来路线图
- 无公开的功能路线图。
- 维护者 Julian Gonggrijp 表示将继续响应安全漏洞修复。
- 项目大概率将在未来数年内维持"安全补丁维护"状态,直到主要依赖框架完成升级。
选型建议(2026)
- 维护旧项目:继续使用,但务必升级到 1.13.8+ 并配置安全扫描。
- 全新项目:不建议新引入 Underscore.js;优先使用原生 JavaScript API,需要工具库时考虑 Lodash 或 Ramda。
- 迁移计划:对于有条件的项目,建议制定渐进式迁移计划,逐步用原生 API 替换 Underscore 方法。