如何在 javascript 中重载函数?

经典(非 js)超载方法:

function myFunc(){
//code
}


function myFunc(overloaded){
//other code
}

Javascript 不允许使用相同的名称定义多个函数:

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

对于 javascript 中的函数重载,除了传递一个包含重载的对象之外,还有其他更好的解决方案吗?

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

125460 次浏览

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

function myFunc(options){
if(typeof options != 'undefined'){
//code
}
}

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

  1. 变量参数 -可以传递不同的参数集(包括类型和数量) ,函数的行为将与传递给它的参数匹配。

  2. 默认参数 -如果没有传递参数,可以为它定义默认值。

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

下面是关于这些类别的论点处理的一个部分。

变量参数

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

JQuery 一直这样做。您可以使一些参数成为可选的,也可以根据传递给函数的参数将其分支。

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

  1. 通过检查声明的参数名称值是否为 undefined,可以检查是否存在任何给定的参数。
  2. 您可以使用 arguments.length检查总量或参数。
  3. 可以检查任何给定参数的类型。
  4. For variable numbers of arguments, you can use the arguments pseudo-array to access any given argument with arguments[i].

下面是一些例子:

Let's look at jQuery's obj.data() method. It supports four different forms of usage:

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");

If the first argument passed to .data() is a string and the second argument is undefined, then the caller must be using this form.


// 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
}
},

这项技术的关键是确保您想要接受的所有形式的参数都是唯一可识别的,而且对于调用者使用的形式从来没有任何混淆。这通常需要对参数进行适当的排序,并确保参数的类型和位置具有足够的唯一性,以便始终能够判断正在使用哪种形式。

For example, if you have a function that takes three string arguments:

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();

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

This creates default properties and values for the start, end and step properties on an object passed to the selectEntries() function.

Default values for function arguments

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

例如:

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


multiply(5); // 5

进一步说明这可以使用 here on MDN的方式。

在 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,如果有人向 foo传入超过2个参数,它就会失败,所以更好的形式是使用模块模式和一些检查:

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;
}());

My personal preference tends to be the switch, although it does bulk up the master function. A common example of where I'd use this technique would be a accessor/mutator method:

function Foo() {} //constructor
Foo.prototype = {
bar: function (val) {
switch (arguments.length) {
case 0:
return this._bar;
case 1:
this._bar = val;
return this;
}
}
}

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"

我使用了一种基于参数数量的稍微不同的重载方法。 然而,我相信约翰福塞特的方法也是好的。 Here the example, code based on John Resig's (jQuery's Author) explanations.

// 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 :)

我试图开发一个简洁的解决方案,这个问题描述了 给你。你可以找到演示 给你。用法如下:

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;
};

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

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;
};

What you are trying to achieve is best done using the function's local 争论 variable.

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


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

你可以找到一个更好的解释如何工作 给你

看看这个:

Http://www.codeproject.com/articles/688869/overloading-javascript-functions

基本上,在你的类中,你对你想要重载的函数进行编号,然后用一个函数调用就可以快速简单地添加函数重载。

因为 JavaScript 没有函数重载,所以可以使用 选项对象代替。如果有一个或两个必需的参数,最好将它们与 options 对象分开。下面的示例说明如何在选项对象中未传递值的情况下将选项对象和填充值用于默认值。

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 + "}";

}

这里 是一个关于如何使用 options 对象的示例

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

<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>

You cannot do method overloading in strict sense. Not like the way it is supported in java or 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: 以上链接到我的个人博客,有更多的详细信息。

在 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 ....
    

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

    function mName(...opt){
console.log(opt);
}
mName(1,2,3,4); //[1,2,3,4]
mName(1,2,3); //[1,2,3]

(() => {
//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...

如何使用代理(ES6功能) ?
我没有发现任何地方提到这种方法。这可能是不切实际的,但它是一个有趣的方法尽管如此。
它类似于 Lua 的元表,您可以使用 __call元方法“重载”调用操作符,以实现重载。
在 JS 中,可以通过代理处理程序中的 apply方法来完成。您可以检查该方法中参数的存在、类型等,而不必在实际函数中进行检查。

proxy apply method

function overloads() {}


overloads.overload1 = (a, b) => {
return a + b;
}
overloads.overload2 = (a, b, c) => {
return a + b + c;
}


const overloadedFn = new Proxy(overloads, { // the first arg needs to be an Call-able object
apply(target, thisArg, args) {
if (args[2]) {
return target.overload2(...args);
}
return target.overload1(...args);
}
})


console.log(overloadedFn(1, 2, 3)); // 6
console.log(overloadedFn(1, 2)); // 3