JavaScript回调函数的警示故事

最近我发现自己经常重复的一条建议是:

在处理JavaScript回调函数时,最好谨慎行事,更加详细。

请注意,我主要是对自己重复这句话,但我认为与世界分享这个建议非常有价值。原因是我遇到了许多问题,这些问题是由于看似无害的函数被用作回调函数而引起的。而且最糟糕的部分还不在于此!当你查看代码时,它们通常会悄悄地溜过去,可能需要第二次或第三次查看才能确定它们是问题背后的罪魁祸首。

我遇到的最常见的错误可能是你熟悉的:在回调函数中使用parseInt(),尤其是与Array.prototype.map()结合使用。考虑以下代码:

const nums = ['1', '5', '10', '21'];
nums.map(parseInt); // [1, NaN, 2, 7]

你发现问题了吗?parseInt()最多接受两个参数:要解析的string和一个可选的radix参数。Array.prototype.map()将三个参数传递给回调函数:valueindexarray。从这个分解中可以明显看出,将每个元素的索引作为基数参数传递导致了这个奇怪的问题。

解决方案也很简单。创建一个函数来传递我们想要的参数给parseInt(),这样可以修复这个问题,并消除潜在的错误:

const nums = ['1', '5', '10', '21'];
nums.map(num => parseInt(num, 10)); // [1, 5, 10, 21]

与此相关的是,在使用第三方库和API时,最好始终创建一个函数来将数据传递给所使用的API的任何部分,而不是直接将其用作回调函数。原因是,即使该库或API现在不需要任何额外的参数,但这可能会在以后的版本中发生变化。如果不考虑这一点,在更新到标记为没有破坏性更改的库的新版本时可能会带来重大风险。看看以下示例:

// third-party-lib@v1.0.0
const parseData = path => {
  const fileData = fs.readFileSync(path);
  return fileData || '';
};

const importantFiles = ['id-card.txt', 'bank-number.txt'];
importantFiles.map(parseData); // 正常工作

// third-party-lib@v1.1.0 - 没有破坏性变化!
const parseData = (path, purge) => {
  const fileData = fs.readFileSync(path);
  if (purge) fs.unlinkSync(path);
  return fileData || '';
};

const importantFiles = ['id-card.txt', 'bank-number.txt'];
importantFiles.map(parseData); // 'bank-number.txt'已被删除

上面的示例虽然有点不太可能,但它演示了一个简单的Array.prototype.map()索引可能会由于外部依赖项的无害版本升级而对整个文件系统造成严重破坏的情况。这是一种很难追踪的错误,当你在调试时努力理解一个没有破坏性变化的版本升级如何导致这种情况时,会带来很多麻烦。

总结一下,在处理回调函数时要特别小心。如果一个函数没有明确设计为回调函数,如果你使用的是第三方代码,即使你不确定,也可以添加一个函数来传递参数。这样做会在长期来看节省你的时间,代价是你的代码看起来稍微冗长一些。我认为这是一个值得的权衡。