- 开发工具
【免费下载链接】just
A library of dependency-free JavaScript utilities that do just one thing.
just-zip-it是just零依赖工具库家族中的数组模块之一,它的职责只有一个:接收任意数量的数组,把每个数组的第 n 个元素取出来,按列分组成一个新的二维数组(即经典的 zip 操作)。本文将以 packages/array-zip/README.md 为核心,完整讲解安装、调用方式与边界行为,并深入其 核心实现 与 测试用例,让读者既能直接上手,也能理解其双指针遍历的底层原理与 TypeScript 类型设计。
认识 just-zip-it:只做一件事的数组拉链工具
just-zip-it是just系列零依赖 npm 模块(zero-dependency npm modules)中的一员,该系列的设计哲学是「每个模块只做一件事」(do just one thing),不捆绑任何第三方运行时依赖,非常适合 PWA 或对包体积敏感的场景。
其官方功能描述只有一句话:Returns an array of grouped elements, taking n-th element from every given array——返回一个分组元素数组,从每个给定数组中取出第 n 个元素组成一组。用直觉类比,它就像拉链(zipper)一样把多个数组「咬合」在一起,按列对齐。
安装与导入
just-zip-it与just系列其他模块一样,同时发布 ESM 与 CJS 两种产物,无需任何转译即可使用。根据 packages/array-zip/package.json 的exports字段配置:
import指向index.mjs(ESM 产物)require指向index.cjs(CommonJS 产物)types指向index.d.ts(类型声明)
npm install just-zip-ityarn add just-zip-itESM 导入(Node / 打包器):
import zip from 'just-zip-it';CommonJS 导入:
const zip = require('just-zip-it');核心用法:从每个数组的第 n 个元素开始分组
这是文档中的全部基础示例,也是该工具最典型的行为。zip接收任意数量的数组作为参数,返回的新数组中,第 n 个元素是一个由「每个原数组的第 n 个元素」组成的子数组:
import zip from 'just-zip-it'; zip([1, 2, 3]); // [[1], [2], [3]] zip([1, 2, 3], ['a', 'b', 'c']); // [[1, 'a'], [2, 'b'], [3, 'c']] zip([1, 2], ['a', 'b'], [true, false]); // [[1, 'a', true], [2, 'b', false]]从这三个例子可以归纳出规则:
- 单个数组:每个元素被包成独立的单元素子数组,即
[[1], [2], [3]]; - 多个等长数组:按索引对齐打包,
[1, 'a']、[2, 'b']、[3, 'c']; - 数组个数不限:子数组的元素个数等于传入的数组个数(如三个数组时每组三个元素)。
边界行为:不等长数组与空数组
不等长数组:用 undefined 补齐缺口
当传入的数组长度不一致时,zip以最长数组的长度为准,较短的数组缺失的位置用undefined补齐:
zip([1, 2, 3], ['a', 'b'], [true]); // [[1, 'a', true], [2, 'b', undefined], [3, undefined, undefined]]这一点在 测试用例 中也被明确验证:zip([1, 2, 3], ['a', 'b'], [true])的结果是[[1, 'a', true], [2, 'b', undefined], [3, undefined, undefined]]。
空数组:返回空结果
当所有传入数组都为空时,结果为空数组:
zip([], []); // []这也是测试文件 test/array-zip/index.cjs 覆盖的用例。
无参数或非数组参数:抛错(注意与 README 的差异)
这里有一个需要特别提醒的坑:当前版本的 README 中保留了以下两条示例注释:
zip(undefined, {}, false, 1, 'foo'); // [] zip([1, 2], ['a', 'b'], undefined, {}, false, 1, 'foo'); // [[1, 'a'], [2, 'b']]然而,这两条示例描述的是历史行为,与当前 3.2.0 版本的实际实现不一致。从 核心实现 可以看出,当前版本对参数做了严格校验:
- 不传任何参数时,抛出
Error('zip requires at least one argument'); - 任一参数不是数组(包括
undefined、null、对象、布尔值、数字、字符串)时,抛出Error('all arguments must be arrays')。
测试用例 也明确断言了四种抛错场景:
zip(); // throws zip(null); // throws zip([1, 2, 3], {a: '1', b: '2', c: '3'}); // throws zip([1, 2], ['a', 'b'], undefined, {}, false, 1, 'foo'); // throws因此在实际使用中,请把「所有参数必须是数组、且至少传一个数组」作为硬性前提;若传入的数据可能混入非数组值,调用前应自行过滤(例如args.filter(Array.isArray))。这也是阅读源码、以测试为准而不仅是依赖 README 示例的典型例子。
源码级原理剖析:双指针单次遍历
just-zip-it的实现非常精巧——没有使用map、reduce或嵌套循环,而是用两个指针在单个while循环中完成了整个 zip 过程。完整实现位于 packages/array-zip/index.cjs,结构如下:
function zip() { var args = Array.prototype.slice.call(arguments); var argsLen = args.length; var maxLen = 0; if (!argsLen) { throw new Error('zip requires at least one argument'); } for (var i = 0; i < argsLen; i++) { if (!Array.isArray(args[i])) { throw new Error('all arguments must be arrays'); } var arrLen = args[i].length; if (arrLen > maxLen) { maxLen = arrLen; } } var result = []; var group = []; var p1 = 0; var p2 = 0; while (p1 < maxLen) { group[p2] = args[p2][p1]; if (p2 < argsLen - 1) { p2++; } else { p2 = 0; result[p1] = group; group = []; p1++; } } return result; }阶段一:参数归一化与校验
Array.prototype.slice.call(arguments)把类数组的arguments转成真正的数组,从而获得.length和下标访问能力;- 随后遍历所有参数:先用
Array.isArray校验类型(不通过则抛错),同时记录所有数组中的最大长度maxLen。maxLen决定了结果数组的长度,也正是不等长数组用undefined补齐的关键——短数组在指针越界处取到的值天然是undefined。
阶段二:双指针构建「列优先」结果
p1表示列号(即当前取的是每个数组的第几个元素),p2表示数组序号(即当前正在从第几个数组取值);- 循环条件
p1 < maxLen保证以最长数组为基准; - 每次迭代执行
group[p2] = args[p2][p1],从第p2个数组中取第p1个元素放入当前分组; - 若
p2还没走到最后一个数组,则p2++继续填充当前分组;当p2到达最后一个数组时,说明这一列(一个分组)已集齐,将其存入result[p1],重置p2 = 0、p1++开始下一列。
整个过程对每个元素恰好访问一次,时间复杂度为O(n × m)(n 为最大数组长度,m 为数组个数),空间复杂度 O(n × m)(结果本身)。由于maxLen由所有数组共同决定,短数组的缺失位置在取值时自然得到undefined,无需任何显式补齐逻辑,这是该实现最简洁之处。
构建产物:index.mjs 由 rollup 自动生成
packages/array-zip/index.mjs 与index.cjs的算法逻辑完全一致(仅导出方式不同,export {arrayZip as default}),它并非手写维护,而是由构建脚本从index.cjs自动生成:每个包的 rollup.config.js 调用统一的 config/createRollupConfig.js,以index.cjs为输入、通过@rollup/plugin-commonjs转译后输出为index.mjs(ESM 格式)。所以社区贡献时只需维护index.cjs一份源码即可。
兼容性推断
从源码结构看,该实现仅使用 ES5 语法(var、Array.prototype.slice、Array.isArray、while循环),没有箭头函数、let/const或扩展运算符,因此可以推断它能在任何支持 ES5 的环境中直接运行,这与just系列「尽量兼容旧平台」的定位一致。
TypeScript 类型设计:元组重载 + readonly 数组
just-zip-it为每个实用工具都提供了类型定义。在 packages/array-zip/index.d.ts 中,zip的类型通过函数重载实现「元组级」的精确推导:
declare function zip<T>(...arr: [ readonly T[] ]): [T][] declare function zip<T, U>(...arr: [ readonly T[], readonly U[] ]): [T, U][] declare function zip<T, U, V>(...arr: [ readonly T[], readonly U[], readonly V[] ]): [T, U, V][] // ……依次到 7 个数组的重载 declare function zip<T, U, V, W, X, Y, Z>(...arr: [ readonly T[], readonly U[], readonly V[], readonly W[], readonly X[], readonly Y[], readonly Z[] ]): [T, U, V, W, X, Y, Z][] declare function zip(...arr: ReadonlyArray<any>[]): any[][]设计要点:
- 针对 1~7 个数组分别声明精确重载:传入
[number, string]两个数组时,返回值类型被推导为[number, string][]; - 支持只读数组:参数类型是
readonly T[],意味着as const声明的只读数组也能直接传入。这一能力在 CHANGELOG.md 的 3.0.5 / 3.0.6 版本中记录为「Change type definition to accept readonly arrays」与「TS: accept read-only arrays」; - 最后一个兜底重载
ReadonlyArray<any>[] => any[][]覆盖 8 个及以上数组或混合类型的场景; - 文件头部的注释
//provide as accurate typing as we can up to a point...也说明:这种逐层重载的重复写法是 TypeScript 元组类型能力的极限所在。
值得留意的是,类型检查与运行时行为存在一处差异:在 packages/array-zip/index.tests.ts 中,zip()在类型层面被标记为 OK(推导为any[][]),但运行时实际会抛错(缺少参数)。这是「类型放宽 + 运行时严格」的典型设计,使用时应以运行时校验为准。
测试验证:三组用例锁定行为
just-zip-it的运行时测试位于 test/array-zip/index.cjs,基于tape编写(测试名会自动加上模块名前缀,见 test/util/test.js),共三组断言:
- 「zips any number of arrays together」:验证任意数量数组的 zip、等长数组对齐、三数组组合,以及
zip([], [])返回空数组; - 「fill in blank spaces with undefined when given arrays of various lengths」:验证不等长数组以
undefined补齐; - 「throws for empty / non-array arguments」:验证无参数、
null、对象、混合非数组参数等场景统一抛错。
在仓库根目录运行npm test(内部依次执行 lint、tape ./test/**/index.cjs、以及基于tsc的类型测试)即可跑通全部用例;根目录的 package.json 还提供了npm run build(lerna 批量构建各包产物)等脚本。
版本演进与维护
从 packages/array-zip/CHANGELOG.md 可以清晰看到该模块的维护脉络:
| 版本 | 变更内容 |
|---|---|
| 3.2.0 | Node 模块后缀由.js重命名为.cjs |
| 3.1.1 | 修复导出顺序,将 default 导出置于末尾(#488) |
| 3.1.0 | package.json 更新(修复 #467 / #483) |
| 3.0.6 / 3.0.5 | 类型定义支持只读数组 |
| 3.0.4 / 3.0.3 | 更新类型定义与.mjs模块 |
| 3.0.2 | 性能优化(即当前双指针实现) |
可以看到,just-zip-it的演进重点是双格式产物(ESM/CJS)、TypeScript 类型完善与性能优化,这正是just系列每个模块的共性工程基线。
典型应用场景
zip是一类高频的「列转行」数据结构工具,在以下场景非常实用:
- 表格/矩阵转置:把多列数据(如
[日期, 温度])按行重组为坐标对序列; - 多序列对齐:将多个传感器的读数序列按索引对齐成一条条记录;
- 与
Object.entries联用:将[[key, value], ...]形式的键值对数组拆回「键数组」与「值数组」; - 测试数据组织:在表格驱动测试中,把「输入数组」与「期望数组」zip 成
[输入, 期望]用例对。
使用时只需记住两条铁律:至少传一个参数,且所有参数必须是数组;如需处理可能混入的非数组值,请在调用前自行过滤。若对实现细节感兴趣,可进一步阅读 packages/array-zip/index.cjs(双指针源码)与 test/array-zip/index.cjs(行为验证),二者共同构成了对该模块最可靠的事实依据。
- 开发工具
【免费下载链接】just
A library of dependency-free JavaScript utilities that do just one thing.
相关推荐
just-flatten-it:零依赖数组扁平化模块的用法与源码解析
just flatten it:零依赖数组扁平化模块的用法与源码解析 本篇以 just 工具库中的 just flatten it https://link.g
开发工具just-flatten-it 数组扁平化实战指南:零依赖、可控深度的 flatten 实现与原理剖析
just flatten it 数组扁平化实战指南:零依赖、可控深度的 flatten 实现与原理剖析 本文围绕 just 仓库中 just flatten i
开发工具just-group-by 源码解读:零依赖数组分组工具的使用与实现原理
just group by 源码解读:零依赖数组分组工具的使用与实现原理 just group by 是 just 库( package.json https:
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考