警惕 lodash.memoize
Lodash 的 memoize 看起来像是免费的性能提升。你只需要包装一个函数,就能获得一个缓存。
但其默认行为隐藏着一个陷阱。它仅根据第一个参数构建缓存键(cache key),而忽略所有其他参数。
如果你只使用一个参数,那是安全的。如果你使用两个或更多参数,就会产生 Bug。
看这个例子:
const add = memoize((a, b) => a + b);
add(1, 2); // 返回 3。它将结果缓存在键为 1 的位置。
add(1, 9); // 返回 3。这是错误的。它应该是 10。
Lodash 再次看到了 1。它在缓存中找到了 1,然后返回了旧的结果。它根本没有去看那个 9。
货币格式化是一个常见的陷阱。
const formatPrice = memoize((amount, currency) =>
new Intl.NumberFormat('en', { style: 'currency', currency }).format(amount)
);
formatPrice(100, 'USD'); // 返回 "$100.00"。键是 100。
formatPrice(100, 'EUR'); // 返回 "$100.00"。这是错误的。
第二次调用忽略了 'EUR'。它在缓存中看到了 100,于是返回了美元而不是欧元。这里没有报错,也没有警告。你只是向用户展示了错误的金额。
你必须提供第二个参数来定义缓存键。这个键必须涵盖每一个输入。
const formatPrice = memoize(
(amount, currency) =>
new Intl.NumberFormat('en', { style: 'currency', currency }).format(amount),
(amount, currency) => `${amount}|${currency}`
);
formatPrice(100, 'USD'); // "$100.00"
formatPrice(100, 'EUR'); // "€100.00"
缓存键必须捕获所有会改变输出的内容。
最后一点思考:记忆化(memoization)会增加风险。只有当一个函数开销很大,并且经常以相同的输入运行时,才使用它。对于简单的函数,直接调用即可。产生 Bug 的风险往往比你获得的性能提升更高。
核心要点:
- 默认键仅使用第一个参数。
- 使用 resolver 将所有参数包含在键中。
- 一个好的键应该反映所有会改变输出的因素。
- 不要对开销小的函数使用 memoize。
Source: https://dev.to/figsify/beware-of-lodashmemoize-it-only-remembers-the-first-argument-4cjl