cherryvenus

JavaScript 函数参数的最佳实践

cherryvenus · 2016-12-18翻译 · 1005阅读 原文链接 十年踪迹审校

常常会有人问一些像“我应该使用对象来传递函数参数吗”这类问题。我确定你知道这种模式,因为这在许多库中很常见:

`$.post({ a: 'lot', of: 'properties', live: 'in', here: 'yep' })`

但是我想说,这实际上不是一个好主意。

让我们看看再 JavaScript 中定义函数参数列表最佳的实践。

以清晰为目的

把对象当作参数传递给函数的问题是你要传入的值指向不清。

像jQuery这样的库,这不是一个大问题——无非看看文档然后就知道你要怎么做了。但是你在工作中看别人写的代码呢?查文档……啊!等等!哪来的文档!Yeah。

尽可能多的文档当然好,山寨的代码库永远不会有那种你需要的文档。如果你需要弄清某个方法需要哪些参数,你就得深入了解源代码。

这个问题可以用一个例子来解释。这里有一个用于对其他函数的调用频率进行限制的函数。

function throttle(o) {
  o.threshhold || (o.threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = o.scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        o.fn.apply(context, args);
      }, o.threshhold);
    } else {
      last = now;
      o.fn.apply(context, args);
    }
  };
}

(源码借鉴了 Remy Sharp)

快说!你可以往参数对象中传什么值?

没有通读整个函数的话,你是找不到关于参数的恼人线索的,甚至你已经读过全部代码之后,还可能会出错。

这是代码原来的函数签名:

function throttle(fn, threshhold, scope)

快说!函数接受哪些参数?

如果你正在拆弹,你需要回答那个问题否则炸弹就会爆炸,而你只有几秒钟的时间思考……我肯定你会觉得有独立的参数的这一版比较好。

好啦,那么第一个最佳实践是:给你的函数使用独立命名的参数

此外,独立的参数如果在一个函数式编程风格中同样很有帮助。在 FP 中经常会部分调用函数(partially apply functions 又叫做偏函数应用,即对一个函数传递一部分参数,然后返回一个新的函数,这个函数接收剩余的参数——译者注),如果你的函数使用对象作为参数,那么会很难做到部分调用。

使用对象参数对于可选项来说挺合适

单独的参数是一个很好的开始,那么我们将会在函数有可选参数的时候遇到问题。

对于只有一个可选参数的函数来说,解决方案很简单:

function stuff(requiredA, requiredB, optional)

最佳实践:可选项放在最后

然而,如果你有多于一个的可选参数,那么问题来了……如果我们只需要传个可选参数要怎么做?

function foo(required, optionalA, optionalB) { /* something */ }

foo('hello', undefined, 1337);

啊!需要填充类似于undefined的值,这让函数不管是调用还是处理参数都变得更复杂了。

在多个可选参数的情况下,我们可以使用对象。这并没有完全解决参数意义不清晰的问题,但这带来的好处是我们不需要传递和处理额外的值。

foo('hello', { optionalB: 1337 });

最佳实践:如果有超过一个可选参数时,使用对象

ES6的好处

最后,如果你使用ES6或者TypeScript,我们可以使用一些新的语法。

对于有可选参数的函数,我们并不清楚这个参数是否可选。有了ES6,我们能够提供一个默认值让这个变得明显:

function something(a, b = 'default value') {
  //if b is not passed, its value becomes 'default value'
}

最佳实践:给可选参数提供默认值

我们也可以在参数列表中同时使用对象解构和默认值。

function foo(required, { optionalA, optionalB } = { }) {
  //available variables:
  //required, optionalA, optionalB
}

foo('hi', { optionalA: 1, optionalB: 1337 });

这给了你两全其美的办法。你可以方便地使用对象作为参数,但是对象的属性能够从函数签名中看到。

最佳实践:当有必要时使用ES6解构

总结

对于上述建议的补充,在函数声明前使用注释块是另一个有用的习惯。这对于阅读函数的人来说非常有用,有了注释,他们能够迅速了解函数的大致信息,不单是函数的参数,还有它们的数据类型和其他注意点。

总结:

  • 使用独立命名的参数

  • 把可选参数放在最后

  • 在有多个可选参数的情况下使用对象

  • 用默认值来表明可选参数

  • 使用解构澄清对象属性

你还可以用 ES6 默认参数做另一件有趣的事情。通常情况下,如果你不传递一个必要的参数,什么事都不会发生。但是有了默认值,我们可以让代码自动抛出一个缺少必要参数的错误!David Walsh 对此技巧有一个极佳的总结