在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都会导致错误。

其他回答

JavaScript中没有真正的函数重载,因为它允许传递任意数量的任意类型的参数。你必须检查函数内部传递了多少参数以及它们的类型。

多年来,我一直在使用这个函数来美化我的重载:

function overload(){
  const fs = arguments, fallback = fs[fs.length - 1];
  return function(){
    const f = fs[arguments.length] || (arguments.length >= fs.length ? fallback : null);
    return f.apply(this, arguments);
  }
}

型:

function curry1(f){
  return curry2(f, f.length);
}

function curry2(f, minimum){
  return function(...applied){
    if (applied.length >= minimum) {
      return f.apply(this, applied);
    } else {
      return curry2(function(...args){
        return f.apply(this, applied.concat(args));
      }, minimum - applied.length);
    }
  }
}

export const curry = overload(null, curry1, curry2);

看看jQuery的off方法:

  function off( types, selector, fn ) {
    var handleObj, type;
    if ( types && types.preventDefault && types.handleObj ) {

        // ( event )  dispatched jQuery.Event
        handleObj = types.handleObj;
        jQuery( types.delegateTarget ).off(
            handleObj.namespace ?
                handleObj.origType + "." + handleObj.namespace :
                handleObj.origType,
            handleObj.selector,
            handleObj.handler
        );
        return this;
    }
    if ( typeof types === "object" ) {

        // ( types-object [, selector] )
        for ( type in types ) {
            this.off( type, selector, types[ type ] );
        }
        return this;
    }
    if ( selector === false || typeof selector === "function" ) {

        // ( types [, fn] )
        fn = selector;
        selector = undefined;
    }
    if ( fn === false ) {
        fn = returnFalse;
    }
    return this.each( function() {
        jQuery.event.remove( this, types, fn, selector );
    } );
  }

许多重载函数在优化性能时几乎不可读。你必须破译函数的头部。这可能比使用我提供的重载函数更快;但是,从人的角度来看,它在识别调用了哪个重载方面要慢一些。

你现在可以在ECMAScript 2018中进行函数重载,而无需填充,检查var长度/类型等,只需使用扩展语法。

function foo(var1, var2, opts){ // set default values for parameters const defaultOpts = { a: [1,2,3], b: true, c: 0.3289, d: "str", } // merge default and passed-in parameters // defaultOpts must go first! const mergedOpts = {...defaultOpts, ...opts}; // you can now refer to parameters like b as mergedOpts.b, // or just assign mergedOpts.b to b console.log(mergedOpts.a); console.log(mergedOpts.b); console.log(mergedOpts.c); console.log(mergedOpts.d); } // the parameters you passed in override the default ones // all JS types are supported: primitives, objects, arrays, functions, etc. let var1, var2="random var"; foo(var1, var2, {a: [1,2], d: "differentString"}); // parameter values inside foo: //a: [1,2] //b: true //c: 0.3289 //d: "differentString"

什么是扩展语法?

ECMAScript提案的Rest/Spread Properties(阶段4)将扩展属性添加到对象字面量中。它将自己的可枚举属性从提供的对象复制到新对象上。 更多关于mdn

注意:对象字面量中的扩展语法在Edge和IE中不起作用,这是一个实验特性。查看浏览器兼容性

我不确定最佳实践,但我是这样做的:

/*
 * Object Constructor
 */
var foo = function(x) {
    this.x = x;
};

/*
 * Object Protoype
 */
foo.prototype = {
    /*
     * f is the name that is going to be used to call the various overloaded versions
     */
    f: function() {

        /*
         * Save 'this' in order to use it inside the overloaded functions
         * because there 'this' has a different meaning.
         */   
        var that = this;  

        /* 
         * Define three overloaded functions
         */
        var f1 = function(arg1) {
            console.log("f1 called with " + arg1);
            return arg1 + that.x;
        }

        var f2 = function(arg1, arg2) {
             console.log("f2 called with " + arg1 + " and " + arg2);
             return arg1 + arg2 + that.x;
        }

        var f3 = function(arg1) {
             console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
             return arg1[0] + arg1[1];
        }

        /*
         * Use the arguments array-like object to decide which function to execute when calling f(...)
         */
        if (arguments.length === 1 && !Array.isArray(arguments[0])) {
            return f1(arguments[0]);
        } else if (arguments.length === 2) {
            return f2(arguments[0], arguments[1]);
        } else if (arguments.length === 1 && Array.isArray(arguments[0])) {
            return f3(arguments[0]);
        }
    } 
}

/* 
 * Instantiate an object
 */
var obj = new foo("z");

/*
 * Call the overloaded functions using f(...)
 */
console.log(obj.f("x"));         // executes f1, returns "xz"
console.log(obj.f("x", "y"));    // executes f2, returns "xyz"
console.log(obj.f(["x", "y"]));  // executes f3, returns "xy"

对于你的用例,这是我用ES6解决它的方法(因为它已经是2017年底了):

const foo = (x, y, z) => {
  if (y && z) {
    // Do your foo(x, y, z); functionality
    return output;
  }
  // Do your foo(x); functionality
  return output;
}

显然,您可以将其用于任何数量的参数,并相应地更改条件语句。