Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[思] 当需要传递多个不定参数时,该如何设计 JavaScript 函数? #30

Open
aleen42 opened this issue Mar 16, 2017 · 2 comments
Assignees

Comments

@aleen42
Copy link
Owner

aleen42 commented Mar 16, 2017

image

其实我们很多时候在设计一个 JavaScript 函数时,都可能会遇到这样的一种情况:参数数量不定,且一般会传递有多个。此时,我们就会想该如何更优雅地设计函数来接收这些不定的参数?此 Issue 的提出就是为了谈谈不才及劣者所知道的一些见解。

对于这样的情况,我们肯定会想到一对函数:

同样是为方法调用指定 this,两者的区别在于传递不定参数的方式。

为了方便记忆,我通常会采用一种巧妙的方法去区分两者。call() 首字母为 c,因而可看作以逗号(Comma)的方式来区分参数,而 apply() 首字母为 a,因而也可看作是以数组(Array)的方式来区分参数。

这样一看,官方似乎已为我们预先提供了两种通用的方式:

  • 以逗号分隔参数
  • 以数组组合参数

然而,若不去亲身实现还不知道该怎么设计函数才能更为优雅?对此,不才与劣者认为应该先实现数组组合的方式,而后再实现逗号分隔的方式。为了能更好地说明,我将举例 underscore 中关于 _.without()_.difference() 的实现。在讨论实现之前,我们先了解这两个函数到底有何作用?其实,它们主要用于过滤数组中的部分成员。因此,通过下面的代码片段我们就能清晰地看到:

var arr = [1, 2, 3, 4, 5];
var result = _.without(arr, 1, 2, 3);
console.log(result); /** => [4, 5] */
var arr = [1, 2, 3, 4, 5];
var result = _.difference(arr, [1, 2, 3], [5, 6]);
console.log(result); /** => [4] */

过滤成员需要通过不定的参数来告知函数,而两者唯一的区别与前述例子类似,也就是传递不定参数的方式不同。那么,回到原来的问题,为何我们要先设计并实现以数组组合方式的函数呢?其实很简单,原因在于反过来实现会造成许多不必要的麻烦。

例如我们先实现数组方式传递的 _.difference(),我们就可以通过简单的数组组合来实现 _.without()

_.difference = function (array) {
    /**
      * 把后续的参数严格铺平成一个数组,即忽略不是包含在数组内的参数
      * 如 _.difference(array, [1, 2], 3); 语句中的 3
      */
    var rest = flatten(arguments, true, true, 1); 

    return _.filter(array, function (value) {
        return !_.contains(rest, value);
    });
};

_.without = function (array) {
    return _.difference(array, Array.prototype.slice.call(arguments, 1));
};

但倘若反过来,实现方式则变得更为复杂:

_.without = function (array) {
    /** 若经过 `_.difference()` 的调用,则还需要把参数进行一层铺平 */
    var rest = (this == 'difference') ?
        _.flatten(arguments, false, false, 1) :
        Array.prototype.slice.call(arguments, 1);

    return _.filter(array, function(value) {
        return !_.contains(rest, value);
    });
};

_.difference = function(array) {
    var args = _.flatten(arguments, true, true, 1);

    return _.without.apply('difference', args.unshift(array));
};

综上所述,以 _.without()_.difference() 为例其复杂点显然在于 _.without() 该如何区分到底是否经过 _.difference() 来调用自己?因为针对这样的两种情况,_.without() 都需要对参数进行不同的处理。简单来说,_.without() 对于来自 _.difference() 的调用需要再进行一次铺平(注:不才与劣者此处是通过指定 this 来提供一种区分的方式)。为何?细致想想就会发现,产生如此的复杂在于旧版的 JavaScript 语法只能通过数组组合来传递若干个不定的参数,而无法铺开成逗号分隔的形式来传递。

那么,既然语法存在缺陷,ES6 是否提供了新的方式去解决该问题呢?不才认为,这恰恰体现出展开操作符(...,Spread Operator)的魅力所在。有了它,你就可以直接展开若干个不定参数呢!

_.without = function (array) {
    var rest = Array.prototype.slice.call(arguments, 1);

    return _.filter(array, function(value) {
        return !_.contains(rest, value);
    });
};

_.difference = function(array) {
    var args = _.flatten(arguments, true, true, 1);
    args.unshift(array);

    return _.without.call(null, ...args);
};
@aleen42 aleen42 self-assigned this Mar 16, 2017
@aleen42 aleen42 changed the title [思] 当参数不定且有多个时,该如何设计 JavaScript 函数? [思] 当需要传递多个不定参数时,该如何设计 JavaScript 函数? Mar 16, 2017
@rockts
Copy link

rockts commented May 28, 2017

大牛啊。感觉很牛逼

@aleen42
Copy link
Owner Author

aleen42 commented May 29, 2017

@rockts
不才及劣者而已

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants