Underscore.js logo

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 个常用的工具方法,涵盖数组操作(eachmapfilterreduce)、对象操作(keysvaluesextendpick)、函数增强(binddebouncethrottlememoize)以及模板系统等,为早期 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 使用 _ 作为全局命名空间,所有工具方法都挂载在 _ 对象上。这种设计避免了全局命名冲突,同时提供了简洁的调用语法。

三大核心模块

  1. 集合操作(Collection)eachmapreducefilterfindwheresortBygroupBy 等,对数组和对象进行统一迭代处理。

  2. 数组方法(Array)firstlastflattencompactdifferenceuniqzipobject 等,专注于数组的数据转换。

  3. 函数工具(Function)binddebouncethrottlememoizedelaydeferonceafterpartialcompose 等,增强函数的行为。

链式调用机制

通过 _.chain() 方法开启链式调用,每次调用返回一个包装对象,最终通过 .value() 获取结果。内部通过 _.mixin() 将所有方法注入到链式调用体系中。

模板引擎

内置轻量级模板引擎 _.template(),支持变量插值、表达式求值和 HTML 转义,可在服务端和客户端使用。

核心特性

  • 轻量级体积:gzip 后约 16KB,远小于 Lodash(~33KB),适合对包体积敏感的场景。
  • 100+ 工具方法:覆盖数组、对象、函数、集合、模板等常用操作场景。
  • 链式调用_.chain() 支持流畅的数据管道操作,提升代码可读性。
  • 模板引擎:内置 _.template() 模板系统,支持自定义分隔符。
  • 函数增强debouncethrottlememoizeonce 等经典函数工具。
  • 深度比较_.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() 提供简单可靠的轻量级模板功能,适合服务端渲染场景。
  • 函数工具方法debouncethrottlememoize 等函数增强工具在各类项目中都有广泛应用。
  • 数据转换管道:链式调用 _.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 中无限制递归导致 DoS1.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.jsLodashRamda原生 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。

最佳实践

生产环境建议

  1. 优先使用原生方法Array.prototype.mapObject.keysArray.prototype.flat 等原生方法在性能上优于工具库方法。只在需要深度比较、复杂模板、函数增强等场景使用 Underscore。

  2. 按需引入减少体积

    // 好 ✅ — 只引入需要的方法
    import { debounce, throttle, memoize } from 'underscore';
     
    // 不好 ❌ — 引入整个库
    import _ from 'underscore';
  3. 锁定安全版本:确保使用 1.13.8+,在 CI 中集成安全扫描。

  4. 模板安全:使用 _.template() 时注意 XSS 防护,对用户输入使用 _.escape() 转义。

  5. 避免深层操作性能陷阱_.isEqual()_.flatten() 对深度嵌套数据存在性能问题,大对象比较建议使用 structuredClone() + 自定义比较或第三方深度比较库。

常见陷阱

  • _.template() 模板注入:不要将用户可控内容直接传入模板字符串,可能导致远程代码执行(RCE)。
  • _.isEqual() 递归溢出:对超深层嵌套对象使用 _.isEqual() 可能导致栈溢出(已在 1.13.8 修复了 CVE-2026-27601 的部分问题,但仍需注意)。
  • 命名空间冲突:如果页面中已有 _ 变量(如 Lodash),使用 _.noConflict() 恢复原始变量。

技术局限与边界

已知限制

  1. 功能停滞:Underscore.js 已进入维护模式,不再添加新功能。新特性(如 Array.prototype.at()Object.hasOwn() 等原生 API 的对应封装)不会被加入。

  2. 性能上限:相比 Lodash 的高度优化实现,Underscore.js 在大数据集处理场景下性能差距明显。

  3. 缺乏函数式范式:不支持自动柯里化、不可变数据管道等函数式编程特性,不适合需要纯函数式风格的场景。

  4. TypeScript 支持薄弱:依赖第三方 @types/underscore,类型定义不如 Lodash(内置类型)或 Ramda 精确。

  5. 模块生态萎缩:相比 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 项目对照原生替代方案。

学习资源

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 方法。