在Javascript中伪造函数重载的最好方法是什么?

我知道在Javascript中不可能像在其他语言中那样重载函数。 如果我需要一个函数有两个使用foo(x)和foo(x,y,z),这是最好的/首选的方式:

一开始就用不同的名字 使用可选参数,如y = y || 'default' 使用参数数量 检查参数类型 还是如何?


当前回答

Typescript手册提到了重载。虽然前面已经提到过检查类型和根据结果执行不同的逻辑,但这种定义多个命名函数来使用类型系统的方法可能会让读者感兴趣。下面是TypeScript如何实现创建一个接受多种类型参数的函数,这些参数将指导函数逻辑根据传入的参数和类型做不同的事情:

答案是为同一个函数提供多个函数类型作为重载列表。这个列表是编译器用来解析函数调用的。让我们创建一个重载列表,用来描述pickCard接收和返回的内容。

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
  // Check to see if we're working with an object/array
  // if so, they gave us the deck and we'll pick the card
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }
  // Otherwise just let them pick the card
  else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [
  { suit: "diamonds", card: 2 },
  { suit: "spades", card: 10 },
  { suit: "hearts", card: 4 },
];

let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

有了这个更改,重载现在为我们提供了对pickCard函数的类型检查调用。

为了让编译器选择正确的类型检查,它遵循与底层JavaScript类似的过程。它查看重载列表,然后继续执行第一个重载,尝试使用提供的参数调用函数。如果找到匹配项,则选择此重载作为正确的重载。出于这个原因,通常按照最特定到最不特定的顺序对重载进行排序。

注意,函数pickCard(x):任何块都不是重载列表的一部分,因此它只有两个重载:一个接受对象,另一个接受数字。用任何其他参数类型调用pickCard都会导致错误。

其他回答

截至2017年7月,以下是常见的技术。注意,我们还可以在函数中执行类型检查。

function f(...rest){   // rest is an array
   console.log(rest.length);
   for (v of rest) if (typeof(v)=="number")console.log(v);
}
f(1,2,3);  // 3 1 2 3

不是每个人都知道可以在函数签名中直接进行解构赋值。

得益于此,您可以轻松地定义非常灵活的方法签名,恕我直言,这比Java方法重载更优越。

例子:

const myFunction = (({a, b, c}) => {
    console.log(a, b, c);
});

myFunction({a: 1, b: 2});
myFunction({a: 1, b: 2, c: 3});

您甚至不需要考虑参数的顺序,并且调用语句和目标方法签名之间具有命名一致性。

你也可以指定默认值:

const myFunction = (({a = 1, b = 2, c} = {}) => {
    console.log(a, b, c);
});

所以我真的很喜欢这种做事的方式,这是我在javascript忍者的秘密中发现的

function addMethod(object,name,fn){
  var old = object[name];
  object[name] = function(){
    if (fn.length == arguments.length){
      return fn.apply(this,arguments);
    } else if(typeof old == 'function'){
        return old.apply(this,arguments);
    }
  }
}

然后使用addMethod将重载函数添加到任何对象。对我来说,这段代码中的主要困惑是fn的使用。Length ==参数。长度,这是因为fn。长度是预期参数的数量,而参数。长度是函数实际调用的参数的数量。匿名函数没有参数的原因是你可以在javascript中传入任意数量的参数,并且语言是宽容的。

我喜欢这个,因为你可以在任何地方使用它-只需创建这个函数,并在任何你想要的代码库中简单地使用这个方法。

它还避免了一个大得离谱的if/switch语句,如果您开始编写复杂的代码,它将变得难以阅读(接受的答案将导致这种结果)。

在缺点方面,我想代码一开始有点晦涩……但我不确定其他人?

我刚试过了,也许能满足你的需要。 根据参数的数量,您可以访问不同的函数。在第一次调用时初始化它。 函数映射隐藏在闭包中。

TEST = {};

TEST.multiFn = function(){
    // function map for our overloads
    var fnMap = {};

    fnMap[0] = function(){
        console.log("nothing here");
        return this;    //    support chaining
    }

    fnMap[1] = function(arg1){
        //    CODE here...
        console.log("1 arg: "+arg1);
        return this;
    };

    fnMap[2] = function(arg1, arg2){
        //    CODE here...
        console.log("2 args: "+arg1+", "+arg2);
        return this;
    };

    fnMap[3] = function(arg1,arg2,arg3){
        //    CODE here...
        console.log("3 args: "+arg1+", "+arg2+", "+arg3);
        return this;
    };

    console.log("multiFn is now initialized");

    //    redefine the function using the fnMap in the closure
    this.multiFn = function(){
        fnMap[arguments.length].apply(this, arguments);
        return this;
    };

    //    call the function since this code will only run once
    this.multiFn.apply(this, arguments);

    return this;    
};

测试它。

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");

有两种方法可以更好地解决这个问题:

如果您希望保留很大的灵活性,则传递一个字典(关联数组) 将一个对象作为参数,并使用基于原型的继承来增加灵活性。