111

经典(非 js)重载方法:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript 不允许使用相同的名称定义多个函数。因此,出现了这样的事情:

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

除了传递带有重载的对象之外,是否有更好的解决方法来处理 javascript 中的函数重载?

传入重载会很快导致函数变得过于冗长,因为每个可能的重载都需要一个条件语句。使用函数来完成//code这些条件语句的内部可能会导致使用范围的棘手情况。

4

15 回答 15

133

Javascript 中的参数重载有多个方面:

  1. 可变参数- 您可以传递不同的参数集(类型和数量),并且函数将以与传递给它的参数匹配的方式运行。

  2. 默认参数- 如果参数未传递,您可以为其定义默认值。

  3. 命名参数- 参数顺序变得无关紧要,您只需命名要传递给函数的参数。

下面是关于这些参数处理类别中的每一个的部分。

变量参数

因为 javascript 没有对参数或所需数量的参数进行类型检查,所以您可以只拥有一个实现,myFunc()通过检查参数的类型、存在或数量来适应传递给它的参数。

jQuery 一直这样做。您可以使某些参数成为可选参数,也可以根据传递给它的参数在函数中分支。

在实现这些类型的重载时,您可以使用几种不同的技术:

  1. 您可以通过检查声明的参数名称值是否为来检查是否存在任何给定参数undefined
  2. 您可以使用 来检查总数量或参数arguments.length
  3. 您可以检查任何给定参数的类型。
  4. 对于可变数量的参数,您可以使用arguments伪数组来访问任何给定的参数arguments[i]

这里有些例子:

让我们看看jQuery的obj.data()方法。它支持四种不同的使用形式:

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

每一个都会触发不同的行为,并且在不使用这种动态形式的重载的情况下,将需要四个单独的函数。

以下是如何辨别所有这些英语选项的方法,然后我将它们全部组合成代码:

// get the data element associated with a particular key value
obj.data("key");

如果传递给的第一个参数.data()是一个字符串,第二个参数是undefined,那么调用者必须使用这种形式。


// set the value associated with a particular key
obj.data("key", value);

如果第二个参数未定义,则设置特定键的值。


// get all keys/values
obj.data();

如果没有传递参数,则返回返回对象中的所有键/值。


// set all keys/values from the passed in object
obj.data(object);

如果第一个参数的类型是普通对象,则设置该对象的所有键/值。


以下是如何将所有这些组合到一组 javascript 逻辑中:

 // method declaration for .data()
 data: function(key, value) {
     if (arguments.length === 0) {
         // .data()
         // no args passed, return all keys/values in an object
     } else if (typeof key === "string") {
         // first arg is a string, look at type of second arg
         if (typeof value !== "undefined") {
             // .data("key", value)
             // set the value for a particular key
         } else {
             // .data("key")
             // retrieve a value for a key
         }
     } else if (typeof key === "object") {
         // .data(object)
         // set all key/value pairs from this object
     } else {
         // unsupported arguments passed
     }
 },

这种技术的关键是确保您想要接受的所有形式的参数都是唯一可识别的,并且永远不会混淆调用者正在使用哪种形式。这通常需要适当地对参数进行排序,并确保参数的类型和位置具有足够的唯一性,以便您始终可以分辨出正在使用哪种形式。

例如,如果您有一个接受三个字符串参数的函数:

obj.query("firstArg", "secondArg", "thirdArg");

您可以轻松地将第三个参数设为可选,并且您可以轻松检测到该条件,但您不能仅将第二个参数设为可选,因为您无法判断调用者要传递的这些参数中的哪一个,因为无法确定第二个参数是否传递参数是第二个参数,或者第二个参数被省略,所以第二个参数的位置实际上是第三个参数:

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

由于所有三个参数都是同一类型,因此您无法区分不同参数之间的区别,因此您不知道调用者的意图。使用这种调用方式,只有第三个参数可以是可选的。如果您想省略第二个参数,则必须将其作为null(或其他可检测的值)传递,并且您的代码会检测到:

obj.query("firstArg", null, "thirdArg");

这是可选参数的 jQuery 示例。这两个参数都是可选的,如果未传递,则采用默认值:

clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
},

这是一个 jQuery 示例,其中可能缺少参数或三种不同类型中的任何一种,这会为您提供四种不同的重载:

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
            this[0].innerHTML.replace(rinlinejQuery, "") :
            null;

    // See if we can take a shortcut and just use innerHTML
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, "<$1></$2>");

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
                if ( this[i].nodeType === 1 ) {
                    jQuery.cleanData( this[i].getElementsByTagName("*") );
                    this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } catch(e) {
            this.empty().append( value );
        }

    } else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery( this );

            self.html( value.call(this, i, self.html()) );
        });

    } else {
        this.empty().append( value );
    }

    return this;
},

命名参数

其他语言(如 Python)允许传递命名参数作为仅传递一些参数并使参数独立于它们传入的顺序的一种方式。Javascript 不直接支持命名参数的特性。一种常用的设计模式是传递属性/值的映射。这可以通过传递具有属性和值的对象来完成,或者在 ES6 及更高版本中,您实际上可以传递 Map 对象本身。

这是一个简单的 ES5 示例:

jQuery$.ajax()接受一种使用形式,您只需将单个参数传递给它,该参数是具有属性和值的常规 Javascript 对象。您传递给它的哪些属性决定了将哪些参数/选项传递给 ajax 调用。有些可能是必需的,许多是可选的。由于它们是对象的属性,因此没有特定的顺序。事实上,可以在该对象上传递超过 30 种不同的属性,只需要一个(url)。

这是一个例子:

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
    // process result here
});

$.ajax()实现内部,它可以只询问传入对象上传递了哪些属性,并将这些属性用作命名参数。这可以通过使用for (prop in obj)或通过将所有属性放入一个数组中Object.keys(obj)然后迭代该数组来完成。

当有大量参数和/或许多参数是可选的时,这种技术在 Javascript 中非常常用。注意:这给实现函数带来了责任,以确保存在最小的有效参数集,并为调用者提供一些调试反馈,如果传递的参数不足(可能通过抛出带有有用错误消息的异常) .

在 ES6 环境中,可以使用解构为上述传递的对象创建默认属性/值。此参考文章中对此进行了更详细的讨论。

这是那篇文章的一个例子:

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};

然后,您可以这样称呼它:

selectEntries({start: 5});
selectEntries({start: 5, end: 10});
selectEntries({start: 5, end: 10, step: 2});
selectEntries({step: 3});
selectEntries();

您未在函数调用中列出的参数将从函数声明中获取它们的默认值。

start这将为,endstep传递给selectEntries()函数的对象的属性创建默认属性和值。

函数参数的默认值

在 ES6 中,Javascript 为参数的默认值添加了内置语言支持。

例如:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

在 MDN 上进一步描述了可以在此处使用的方式。

于 2012-06-01T19:02:59.210 回答
33

可以通过多种方式重载 JavaScript 中的函数。所有这些都涉及单个主功能,该功能要么执行所有流程,要么委托给子功能/流程。

最常见的简单技术之一涉及一个简单的开关:

function foo(a, b) {
    switch (arguments.length) {
    case 0:
        //do basic code
        break;
    case 1:
        //do code with `a`
        break;
    case 2:
    default:
        //do code with `a` & `b`
        break;
    }
}

更优雅的技术是使用数组(或对象,如果您不为每个参数计数进行重载):

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

前面的例子不是很优雅,任何人都可以修改fooArr,如果有人将超过 2 个参数传递给 ,它将失败foo,因此更好的形式是使用模块模式和一些检查:

var foo = (function () {
    var fns;
    fns = [
        function () {
        },
        function (a) {
        },
        function (a, b) {
        }
    ];
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = foo.length;
        }
        return fns[fnIndex].call(this, a, b);
    }
    return foo;
}());

当然,您的重载可能希望使用动态数量的参数,因此您可以为fns集合使用一个对象。

var foo = (function () {
    var fns;
    fns = {};
    fns[0] = function () {
    };
    fns[1] = function (a) {
    };
    fns[2] = function (a, b) {
    };
    fns.params = function (a, b /*, params */) {
    };
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = 'params';
        }
        return fns[fnIndex].apply(this, Array.prototype.slice.call(arguments));
    }
    return foo;
}());

我个人的偏好往往是switch,尽管它确实增加了 master 功能。我使用这种技术的一个常见例子是访问器/修改器方法:

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}
于 2012-06-01T19:03:57.680 回答
10

你不能严格意义上的方法重载。不像javaor中支持它的方式c#

问题是 JavaScript 本身并不支持方法重载。因此,如果它看到/解析两个或多个具有相同名称的函数,它只会考虑最后定义的函数并覆盖以前的函数。

我认为适合大多数情况的一种方式如下 -

假设你有方法

function foo(x)
{
} 

您可以定义一个新方法,而不是在 javascript 中不可能的重载方法

fooNew(x,y,z)
{
}

然后修改第一个函数如下 -

function foo(x)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

如果您有许多这样的重载方法,请考虑使用switch不仅仅是if-else语句。

更多详细信息) PS:上面的链接转到我的个人博客,其中有更多详细信息。

于 2014-09-13T06:45:53.200 回答
4

在javascript中,您可以只实现一次函数并在没有参数的情况下调用函数myFunc() 然后检查选项是否为“未定义”

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}
于 2012-06-01T19:01:31.817 回答
4

我根据参数编号使用了一些不同的重载方法。不过我相信 John Fawcett 的方法也不错。这里的示例,代码基于 John Resig(jQuery 的作者)的解释。

// o = existing object, n = function name, f = function.
    function overload(o, n, f){
        var old = o[n];
        o[n] = function(){
            if(f.length == arguments.length){
                return f.apply(this, arguments);
            }
            else if(typeof o == 'function'){
                return old.apply(this, arguments);
            }
        };
    }

可用性:

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)
于 2012-12-23T09:21:56.800 回答
4

我试图为这里描述的这个问题开发一个优雅的解决方案。你可以在这里找到演示。用法如下所示:

var out = def({
    'int': function(a) {
        alert('Here is int '+a);
    },

    'float': function(a) {
        alert('Here is float '+a);
    },

    'string': function(a) {
        alert('Here is string '+a);
    },

    'int,string': function(a, b) {
        alert('Here is an int '+a+' and a string '+b);
    },
    'default': function(obj) {
        alert('Here is some other value '+ obj);
    }

});

out('ten');
out(1);
out(2, 'robot');
out(2.5);
out(true);

用于实现此目的的方法:

var def = function(functions, parent) {
 return function() {
    var types = [];
    var args = [];
    eachArg(arguments, function(i, elem) {
        args.push(elem);
        types.push(whatis(elem));
    });
    if(functions.hasOwnProperty(types.join())) {
        return functions[types.join()].apply(parent, args);
    } else {
        if (typeof functions === 'function')
            return functions.apply(parent, args);
        if (functions.hasOwnProperty('default'))
            return functions['default'].apply(parent, args);        
    }
  };
};

var eachArg = function(args, fn) {
 var i = 0;
 while (args.hasOwnProperty(i)) {
    if(fn !== undefined)
        fn(i, args[i]);
    i++;
 }
 return i-1;
};

var whatis = function(val) {

 if(val === undefined)
    return 'undefined';
 if(val === null)
    return 'null';

 var type = typeof val;

 if(type === 'object') {
    if(val.hasOwnProperty('length') && val.hasOwnProperty('push'))
        return 'array';
    if(val.hasOwnProperty('getDate') && val.hasOwnProperty('toLocaleTimeString'))
        return 'date';
    if(val.hasOwnProperty('toExponential'))
        type = 'number';
    if(val.hasOwnProperty('substring') && val.hasOwnProperty('length'))
        return 'string';
 }

 if(type === 'number') {
    if(val.toString().indexOf('.') > 0)
        return 'float';
    else
        return 'int';
 }

 return type;
};
于 2013-04-10T01:22:09.837 回答
3

https://github.com/jrf0110/leFunc

var getItems = leFunc({
  "string": function(id){
    // Do something
  },
  "string,object": function(id, options){
    // Do something else
  },
  "string,object,function": function(id, options, callback){
    // Do something different
    callback();
  },
  "object,string,function": function(options, message, callback){
    // Do something ca-raaaaazzzy
    callback();
  }
});

getItems("123abc"); // Calls the first function - "string"
getItems("123abc", {poop: true}); // Calls the second function - "string,object"
getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"
于 2012-06-23T22:05:35.773 回答
3

JS重载没问题,pb函数重载时如何保持代码干净?

您可以使用转发来获得干净的代码,基于两件事:

  1. 参数数量(调用函数时)。
  2. 参数类型(调用函数时)

      function myFunc(){
          return window['myFunc_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }
    
        /** one argument & this argument is string */
      function myFunc_1_string(){
    
      }
       //------------
       /** one argument & this argument is object */
      function myFunc_1_object(){
    
      }
      //----------
      /** two arguments & those arguments are both string */
      function myFunc_2_string_string(){
    
      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function myFunc_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }
    
       //--- And so on ....   
    
于 2016-07-19T08:09:52.593 回答
2

看一下这个:

http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions

基本上在你的课堂上,你给你想要重载的函数编号,然后通过一个函数调用你添加函数重载,快速而简单。

于 2013-12-03T08:44:15.700 回答
2

由于 JavaScript 没有函数重载选项,因此可以使用对象来代替。如果有一个或两个必需的参数,最好将它们与选项对象分开。这是一个关于如何使用选项对象并将值填充为默认值的示例,以防选项对象中未传递值。

function optionsObjectTest(x, y, opts) {
    opts = opts || {}; // default to an empty options object

    var stringValue = opts.stringValue || "string default value";
    var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
    var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;

    return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";

}

是一个关于如何使用选项对象的示例

于 2014-01-09T02:44:30.763 回答
2

为此,您需要创建一个将函数添加到对象的函数,然后它将根据您发送给函数的参数数量执行:

<script > 
//Main function to add the methods
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);
  };
}


  var ninjas = {
   values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
};

//Here we declare the first function with no arguments passed
  addMethod(ninjas, "find", function(){
    return this.values;
});

//Second function with one argument
  addMethod(ninjas, "find", function(name){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i].indexOf(name) == 0)
        ret.push(this.values[i]);
    return ret;
  });

//Third function with two arguments
  addMethod(ninjas, "find", function(first, last){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i] == (first + " " + last))
        ret.push(this.values[i]);
    return ret;
  });


//Now you can do:
ninjas.find();
ninjas.find("Sam");
ninjas.find("Dean", "Edwards")
</script>
于 2014-08-26T20:40:57.530 回答
0

我喜欢在父函数中添加子函数,以实现区分相同功能的参数组的能力。

var doSomething = function() {
    var foo;
    var bar;
};

doSomething.withArgSet1 = function(arg0, arg1) {
    var obj = new doSomething();
    // do something the first way
    return obj;
};

doSomething.withArgSet2 = function(arg2, arg3) {
    var obj = new doSomething();
    // do something the second way
    return obj;
};
于 2013-06-17T18:23:38.803 回答
0

您要达到的目标最好使用函数的局部参数变量来完成。

function foo() {
    if (arguments.length === 0) {
        //do something
    }
    if (arguments.length === 1) {
        //do something else
    }
}

foo(); //do something
foo('one'); //do something else

您可以在此处找到有关其工作原理的更好解释。

于 2013-10-08T05:35:49.187 回答
0

使用扩展运算符作为参数怎么样?可以使用多个参数调用同一个块。所有参数都被添加到一个数组中,并且在您可以根据长度循环进入的方法中。

    function mName(...opt){
        console.log(opt); 
    }
    mName(1,2,3,4); //[1,2,3,4]
    mName(1,2,3); //[1,2,3]
于 2021-01-01T12:30:58.473 回答
0

(() => {
  //array that store functions
    var Funcs = []
     /**
     * @param {function} f overload function
     * @param {string} fname overload function name
   * @param {parameters} vtypes function parameters type descriptor (number,string,object....etc
     */
    overloadFunction = function(f, fname, ...vtypes) {
        var k,l, n = false;
        if (!Funcs.hasOwnProperty(fname)) Funcs[fname] = [];
        Funcs[fname].push([f, vtypes?vtypes: 0 ]);
        window[fname] = function() {
            for (k = 0; k < Funcs[fname].length; k++)
                if (arguments.length == Funcs[fname][k][0].length) {
                    n=true;
                    if (Funcs[fname][k][1]!=0)
                    for(i=0;i<arguments.length;i++)
                    {
                        if(typeof arguments[i]!=Funcs[fname][k][1][i])
                        {
                            n=false;
                        }
                    }
                    if(n) return Funcs[fname][k][0].apply(this, arguments);
                }
        }
    }
})();

//First sum function definition with parameter type descriptors
overloadFunction(function(a,b){return a+b},"sum","number","number")
//Second sum function definition with parameter with parameter type descriptors
overloadFunction(function(a,b){return a+" "+b},"sum","string","string")
//Third sum function definition (not need parameter type descriptors,because no other functions with the same number of parameters
overloadFunction(function(a,b,c){return a+b+c},"sum")

//call first function
console.log(sum(4,2));//return 6
//call second function
console.log(sum("4","2"));//return "4 2"
//call third function
console.log(sum(3,2,5));//return 10
//ETC...

于 2021-12-21T05:07:34.170 回答