es-toolkit 兼容层 multiply 函数完全指南:与 lodash 一致的乘法实现及源码剖析
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
本文围绕 es-toolkit 兼容模块(es-toolkit/compat)中的multiply函数展开,系统讲解它的调用方式、边界行为、与原生*运算符的性能取舍,并结合仓库源码与测试用例剖析其底层实现原理,同时介绍函数式版本(es-toolkit/fp)的用法,帮助你安全地从 lodash 迁移或在日常项目中正确使用该 API。
快速导读
multiply用于计算两个数字的乘积,是 lodash 兼容 API 之一。在 multiply 兼容层参考文档 中,官方明确指出:该函数因额外的函数调用开销而运行较慢,推荐直接使用*运算符。因此它真正的价值场景是:保持与 lodash 完全一致的语义(包括参数强制转换、缺省参数、NaN 传播等行为),用于 lodash 迁移或需要在统一 API 抽象下工作的代码。阅读完本文,你将掌握multiply的完整参数语义、返回值规则、源码级实现细节,以及函数式版本es-toolkit/fp中柯里化multiply的组合用法。
一、基本用法与签名
multiply接受两个数字参数,返回它们的乘积:
const result = multiply(value, other);从 兼容层源码 可以看到其类型签名:
export function multiply(value: number, other: number): number;该函数从es-toolkit/compat入口导入,并在 compat.ts 中通过export { multiply } from './math/multiply.ts'对外暴露:
import { multiply } from 'es-toolkit/compat'; // 基本乘法 multiply(2, 3); // Returns: 6 multiply(4, 5); // Returns: 20 // 负数处理 multiply(2, -3); // Returns: -6 multiply(-4, -5); // Returns: 20 // 小数处理 multiply(2.5, 4); // Returns: 10 // NaN 处理 multiply(NaN, 3); // Returns: NaN multiply(2, NaN); // Returns: NaN multiply(NaN, NaN); // Returns: NaN参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
value | number | 乘法中的第一个数字 |
other | number | 乘法中的第二个数字 |
返回值
返回两个数字相乘的结果(number)。如果任一参数为NaN,则返回NaN。
二、为什么官方建议改用*运算符
参考文档在开头放置了醒目的::: warning警告块,这在 es-toolkit 文档体系中属于"不推荐在常规代码中使用"的信号。原因非常直接:
This
multiplyfunction works slowly due to additional function calls. Use the faster and simpler*operator instead.
翻译过来即:multiply因额外的函数调用而运行较慢,请改用更快更简单的*运算符。es-toolkit 的核心卖点之一就是性能与体积(项目描述中强调"2-3 倍更快、体积缩小最高 97%"),因此在可以直接书写a * b的地方,multiply(a, b)没有任何性能优势。
那么multiply存在的意义是什么?答案是兼容性与语义一致性:当你的代码需要以(value, other) => number这样的统一函数签名被传递(例如作为回调、被高阶函数调用),或者你正在从 lodash 迁移、希望获得与 lodash 完全一致的行为时,multiply能保证语义不出现偏差。而这一点,正是下文源码剖析所要揭示的。
三、源码级实现剖析
multiply 兼容层实现 的完整代码如下:
import { toNumber } from '../util/toNumber.ts'; import { toString } from '../util/toString.ts'; export function multiply(value: number, other: number): number { if (value === undefined && other === undefined) { return 1; } if (value === undefined || other === undefined) { return value ?? other; } if (typeof value === 'string' || typeof other === 'string') { value = toString(value) as any; other = toString(other) as any; } else { value = toNumber(value); other = toNumber(other); } return value * other; }虽然公开签名是(value: number, other: number),但实现里做了大量防御性处理,这正是 lodash 兼容语义的核心所在。
1. 两个参数都缺省时返回 1
if (value === undefined && other === undefined) { return 1; }当multiply()以零参数调用时,返回1(乘法单位元)。这一点在 测试用例 中有明确验证:
// @ts-expect-error - invalid arguments expect(multiply()).toBe(1);2. 只有一个参数时返回该参数本身
if (value === undefined || other === undefined) { return value ?? other; }当只传入一个参数时,直接返回那个参数(等价于"乘以 1")。对应测试见 multiply.spec.ts:
// @ts-expect-error - invalid arguments expect(multiply(6)).toBe(6); // @ts-expect-error - invalid arguments expect(multiply(6, undefined)).toBe(6); // @ts-expect-error - invalid arguments expect(multiply(undefined, 4)).toBe(4);3. 参数强制转换(coercion)逻辑
if (typeof value === 'string' || typeof other === 'string') { value = toString(value) as any; other = toString(other) as any; } else { value = toNumber(value); other = toNumber(other); }这是兼容 lodash 行为的关键分叉:
- 只要任一参数是字符串,两个参数都先经过
toString处理再相乘; - 否则,两个参数都通过
toNumber转成数字。
由此引出一个重要行为:multiply('6', '4')返回24,而multiply('x', 'y')返回NaN。测试见 multiply.spec.ts。
其中toNumber来自 util/toNumber.ts,它的特别之处在于:
export function toNumber(value: any): number { if (isSymbol(value)) { return NaN; } return Number(value); }与原生Number()不同,toNumber对Symbol 返回NaN。这使得multiply(0, symbol)和multiply(symbol, 0)都会得到NaN,对应测试见 multiply.spec.ts。同理,对象也会被转换为NaN:multiply(0, {})与multiply({}, 0)均为NaN,见 multiply.spec.ts。
4. 符号零(-0)的符号保留
一个容易被忽视的兼容细节是:multiply会正确保留-0的符号。测试用例如下(multiply.spec.ts):
const values = [0, '0', -0, '-0']; const expected = [ [0, Infinity], ['0', Infinity], [-0, -Infinity], ['-0', -Infinity], ];即1 / -0 === -Infinity,说明-0的符号没有被破坏。由于实现最终走的是原生value * other,JavaScript 的乘法语义天然保留了这一行为。
5. NaN 传播
由于最终执行value * other,只要任一操作数为NaN,结果即为NaN。参考文档与测试(multiply.spec.ts)都覆盖了multiply(NaN, 3)、multiply(3, NaN)、multiply(NaN, NaN)三种情况。
四、函数式版本:es-toolkit/fp中的 multiply
除兼容层外,es-toolkit 还在es-toolkit/fp中提供了柯里化版本的multiply,其文档见 fp 版 multiply 参考,实现位于 src/fp/math/multiply.ts:
export function multiply(multiplicand: number): (value: number) => number { return function (value: number): number { return value * multiplicand; }; }它的签名是multiply(multiplicand) => (value) => value * multiplicand,专为组合场景设计,可配合pipe或作为map的回调使用:
import { map, multiply, pipe } from 'es-toolkit/fp'; // 转换管道中的值 pipe(3, multiply(2)); // => 6 // 作为 map 回调使用 pipe([1, 2, 3], map(multiply(3))); // => [3, 6, 9]对应测试见 fp/math/multiply.spec.ts,覆盖了管道乘法、map 回调、乘以 0、负乘数等场景:
expect(pipe(3, multiply(2))).toBe(6); expect(pipe([1, 2, 3], map(multiply(3)))).toEqual([3, 6, 9]); expect(pipe(42, multiply(0))).toBe(0); expect(pipe(5, multiply(-2))).toBe(-10);注意:fp 版本是纯柯里化实现,不包含兼容层的缺省参数、字符串强制转换等防御逻辑——它要求multiplicand是真正的数字。如果需要在管道中处理带强转语义的乘法,请使用兼容层版本。
五、兼容层与 fp 版本的选择建议
| 场景 | 推荐写法 |
|---|---|
| 普通代码中的两个数字相乘 | 直接使用a * b运算符(最快、最简单) |
| 从 lodash 迁移,需要完全一致的语义 | import { multiply } from 'es-toolkit/compat' |
在pipe/map等组合场景中乘法 | import { multiply } from 'es-toolkit/fp' |
参考文档中的警告同样适用于 fp 版本:凡是能用*直接书写的地方,都应优先使用运算符。multiply的核心价值是语义兼容与可组合性,而不是性能。
六、小结
multiply(value, other)返回两数乘积,任一参数为NaN时返回NaN;- 兼容层实现包含完整的 lodash 兼容语义:零参数返回
1、单参数返回自身、字符串参数强制转换、Symbol/对象转NaN、-0符号保留; - 由于存在额外的函数调用与类型转换开销,官方明确建议常规场景使用
*运算符; es-toolkit/fp提供柯里化版本,适合与pipe、map等组合工具搭配使用。
相关参考:兼容层文档 multiply、fp 版文档 multiply (FP)、兼容层实现 src/compat/math/multiply.ts、fp 实现 src/fp/math/multiply.ts、测试用例 compat/multiply.spec.ts 与 fp/multiply.spec.ts。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考