警惕 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 的风险往往比你获得的性能提升更高。

核心要点:

Source: https://dev.to/figsify/beware-of-lodashmemoize-it-only-remembers-the-first-argument-4cjl