JavaScript中的函数重载-最佳实践

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

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

  1. 一开始用不同的名字
  2. 使用可选参数,如y = y || 'default'
  3. 使用参数数
  4. 检查参数类型
  5. 或者怎么做?
561419 次浏览

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

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

  1. 传递一个字典(关联数组)如果你想留下很大的灵活性

  2. 以一个对象作为参数,并使用基于原型的继承来增加灵活性。

最好的方法实际上取决于函数和参数。您的每个选项在不同的情况下都是一个好主意。我通常按照以下顺序尝试这些,直到其中一个有效:

  1. 使用可选参数,如y=y||'default'。如果你能做到这一点,这很方便,但实际上它可能并不总是有效,例如当0/空/未定义是有效参数时。

  2. 使用参数的数量。与最后一个选项类似,但当#1不起作用时可能会起作用。

  3. 检查参数的类型。这在某些参数数量相同的情况下可以工作。如果你不能可靠地确定类型,你可能需要使用不同的名称。

  4. 首先使用不同的名称。如果其他选项不起作用、不实用或为了与其他相关功能保持一致,您可能需要这样做。

使用参数进行函数重载的最佳方法不是检查参数长度或类型;检查类型只会让你的代码变慢,让你享受数组、空值、对象等的乐趣。

大多数开发人员所做的是将对象作为他们方法的最后一个参数。这个对象可以容纳任何东西。

function foo(a, b, opts) {// ...if (opts['test']) { } //if test param exists, do something..}

foo(1, 2, {"method":"add"});foo(3, 4, {"test":"equals", "bar":"tree"});

然后你可以在你的方法中以任何你想要的方式处理它。[Switch、if-else等]

没有办法在javascript中重载函数。所以,我推荐像下面这样的typeof()方法,而不是多个函数来伪造重载。

function multiTypeFunc(param){if(typeof param == 'string') {alert("I got a string type parameter!!");}else if(typeof param == 'number') {alert("I got a number type parameter!!");}else if(typeof param == 'boolean') {alert("I got a boolean type parameter!!");}else if(typeof param == 'object') {alert("I got a object type parameter!!");}else{alert("error : the parameter is undefined or null!!");}}

祝你好运!

这是一种允许使用参数类型重载真实方法的方法,如下所示:

Func(new Point());Func(new Dimension());Func(new Dimension(), new Point());Func(0, 0, 0, 0);

编辑(2018):自2011年编写以来,直接方法调用的速度大大提高,而重载方法的速度却没有。

这不是我推荐的方法,但这是一个值得思考的练习,思考如何解决这些类型的问题。


这是不同方法的基准-https://jsperf.com/function-overloading。它表明,从16.0(beta)开始,函数重载(考虑类型)在GoogleChromeV8中可能在慢13倍左右。

除了传递一个对象(即{x: 0, y: 0})之外,还可以在适当的时候采用C方法,相应地命名方法。例如,Vector. AddVector(向量)、Vector. AddIntegers(x, y, z,…)和Vector. AddArray(integerArray)。您可以查看C库,例如OpenGL来命名灵感。

编辑:我添加了一个基准来传递对象并使用'param' in argarg.hasOwnProperty('param')测试对象,函数重载比传递对象和检查属性要快得多(至少在这个基准中)。

从设计的角度看,函数重载只有当重载的参数对应于相同的操作时才有效或合乎逻辑。因此,应该有一个只关心特定细节的底层方法,否则可能表明不合适的设计选择。因此,还可以通过将数据转换为各自的对象来解决函数重载的使用。当然,必须考虑问题的范围,因为如果你的意图只是打印一个名称,没有必要做精细的设计,但对于框架和库的设计,这种想法是有道理的。

我的例子来自Rectangle的实现——因此提到了Dimension和Point。也许Rectangle可以向DimensionPoint原型添加GetRectangle()方法,然后对函数重载问题进行排序。那么原语呢?好吧,我们有参数长度,这现在是一个有效的测试,因为对象有GetRectangle()方法。

function Dimension() {}function Point() {}
var Util = {};
Util.Redirect = function (args, func) {'use strict';var REDIRECT_ARGUMENT_COUNT = 2;
if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {return null;}
for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {var argsIndex = i-REDIRECT_ARGUMENT_COUNT;var currentArgument = args[argsIndex];var currentType = arguments[i];if(typeof(currentType) === 'object') {currentType = currentType.constructor;}if(typeof(currentType) === 'number') {currentType = 'number';}if(typeof(currentType) === 'string' && currentType === '') {currentType = 'string';}if(typeof(currentType) === 'function') {if(!(currentArgument instanceof currentType)) {return null;}} else {if(typeof(currentArgument) !== currentType) {return null;}}}return [func.apply(this, args)];}
function FuncPoint(point) {}function FuncDimension(dimension) {}function FuncDimensionPoint(dimension, point) {}function FuncXYWidthHeight(x, y, width, height) { }
function Func() {Util.Redirect(arguments, FuncPoint, Point);Util.Redirect(arguments, FuncDimension, Dimension);Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);}
Func(new Point());Func(new Dimension());Func(new Dimension(), new Point());Func(0, 0, 0, 0);

我经常这样做:

C#:

public string CatStrings(string p1)                  {return p1;}public string CatStrings(string p1, int p2)          {return p1+p2.ToString();}public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}
CatStrings("one");        // result = oneCatStrings("one",2);      // result = one2CatStrings("one",2,true); // result = one2true

JavaScript等效:

function CatStrings(p1, p2, p3){var s = p1;if(typeof p2 !== "undefined") {s += p2;}if(typeof p3 !== "undefined") {s += p3;}return s;};
CatStrings("one");        // result = oneCatStrings("one",2);      // result = one2CatStrings("one",2,true); // result = one2true

这个特殊的例子在javascript中实际上比C#更优雅。未指定的参数在javascript中是“未定义的”,在if语句中计算为false。然而,函数定义并没有传达p2和p3是可选的信息。如果你需要大量重载,jQuery决定使用一个对象作为参数,例如,jQuery.ajax(选项)。我同意他们的观点,这是最强大且可清晰记录的重载方法,但我很少需要超过一两个快速可选参数。

编辑:根据Ian的建议更改IF测试

看看这个。它非常酷。欺骗Javascript允许您进行这样的调用:

var users = new Users();users.find(); // Finds allusers.find("John"); // Finds users by nameusers.find("John", "Resig"); // Finds users by first and last name

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

TEST = {};
TEST.multiFn = function(){// function map for our overloadsvar 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 closurethis.multiFn = function(){fnMap[arguments.length].apply(this, arguments);return this;};
//    call the function since this code will only run oncethis.multiFn.apply(this, arguments);
return this;};

测试一下。

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

第一个选项确实值得注意,因为这是我在相当复杂的代码设置中遇到的问题。所以,我的答案是

  1. 一开始用不同的名字

有一个小但基本的提示,名称应该看起来不同的计算机,但不适合你。名称重载函数,如:func、函数1、函数2。

我们做了over.js来解决这个问题是一个非常优雅的方法。你可以这样做:

var obj = {
/*** Says something in the console.** say(msg) - Says something once.* say(msg, times) - Says something many times.*/say: Over(function(msg$string){console.info(msg$string);},function(msg$string, times$number){for (var i = 0; i < times$number; i++) this.say(msg$string);})
};

我正在开发一个为JavaScript提供类代码功能的库,目前它支持构造函数、继承、通过参数数量和参数类型重载的方法、混合、静态属性和单例。

请参阅使用该库的方法重载示例:

eutsiv.define('My.Class', {constructor: function() {this.y = 2;},x: 3,sum: function() {return this.x + this.y;},overloads: {value: [function() { return this.x + ', ' + this.y },function(p1) { this.x = p1; },function(p1, p2) { this.x = p1; this.y = p2; }  // will set x and y]}});
var test = new My.Class({ x: 5 });   // create the objecttest.value();                        // will return '5, 2'test.sum();                          // will return 7test.value(13);                      // will set x to 13test.value();                        // will return '13, 2'test.sum();                          // will return 15test.value(10, 20);                  // will set x to 10 and y to 20test.value();                        // will return '10, 20'test.sum();                          // will return 30

欢迎任何反馈,bug修复,文档和测试改进!

https://github.com/eutsiv/eutsiv.js

由于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 patternvar numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;
return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";
}

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

所以我真的很喜欢这种我在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);}}}

然后使用addWay向任何对象添加重载函数。这段代码中的主要困惑是使用fn.length==arguments.length-这是有效的,因为fn.length是预期参数的数量,而arguments.length是实际与函数一起调用的参数的数量。匿名函数没有参数的原因是因为您可以在javascript中传递任意数量的参数,并且语言是宽容的。

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

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

就缺点而言,我想代码最初有点模糊……但我不确定其他人?

由于这篇文章已经包含了很多不同的解决方案,我想我发布了另一个。

function onlyUnique(value, index, self) {return self.indexOf(value) === index;}
function overload() {var functions = arguments;var nroffunctionsarguments = [arguments.length];for (var i = 0; i < arguments.length; i++) {nroffunctionsarguments[i] = arguments[i].length;}var unique = nroffunctionsarguments.filter(onlyUnique);if (unique.length === arguments.length) {return function () {var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);return functions[indexoffunction].apply(this, arguments);}}else throw new TypeError("There are multiple functions with the same number of parameters");
}

这可以如下所示使用:

var createVector = overload(function (length) {return { x: length / 1.414, y: length / 1.414 };},function (a, b) {return { x: a, y: b };},function (a, b,c) {return { x: a, y: b, z:c};});console.log(createVector(3, 4));console.log(createVector(3, 4,5));console.log(createVector(7.07));

这个解决方案并不完美,但我只想展示如何做到这一点。

这是一个老问题,但我认为需要另一个条目(尽管我怀疑有人会阅读它)。立即调用函数表达式(IIFE)的使用可以与闭包和内联函数结合使用,以允许函数重载。考虑以下(人为的)示例:

var foo;
// original 'foo' definitionfoo = function(a) {console.log("a: " + a);}
// define 'foo' to accept two argumentsfoo = (function() {// store a reference to the previous definition of 'foo'var old = foo;
// use inline function so that you can refer to it internallyreturn function newFoo(a,b) {
// check that the arguments.length == the number of arguments// defined for 'newFoo'if (arguments.length == newFoo.length) {console.log("a: " + a);console.log("b: " + b);
// else if 'old' is a function, apply it to the arguments} else if (({}).toString.call(old) === '[object Function]') {old.apply(null, arguments);}}})();
foo(1);> a: 1foo(1,2);> a: 1> b: 2foo(1,2,3)> a: 1

简而言之,IIFE的使用创建了一个局部作用域,允许我们定义私有变量old来存储对函数foo初始定义的引用。然后,该函数返回一个内联函数newFoo,如果它正好传递了两个参数ab,则记录两个参数的内容,如果arguments.length !== 2,则调用old函数。这种模式可以重复任意次数,以赋予一个变量几个不同的函数定义。

您可以使用John Resig的“addWay”。使用此方法,您可以根据参数计数“重载”方法。

// addMethod - By John Resig (MIT Licensed)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 - By Stavros Ioannidisfunction addMethod(obj, name, fn) {obj[name] = obj[name] || function() {// get the cached method with arguments.length argumentsvar method = obj[name].cache[arguments.length];
// if method exists call itif ( !! method)return method.apply(this, arguments);else throw new Error("Wrong number of arguments");};
// initialize obj[name].cacheobj[name].cache = obj[name].cache || {};
// Check if a method with the same number of arguments existsif ( !! obj[name].cache[fn.length])throw new Error("Cannot define multiple '" + name +"' methods with the same number of arguments!");
// cache the method with fn.length argumentsobj[name].cache[fn.length] = function() {return fn.apply(this, arguments);};}

如果我需要一个函数有两个用途foo(x)和foo(x, y, z),哪个是最好/首选的方法?

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

我认为适合大多数情况的方法之一是

假设你有方法

function foo(x){}

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

fooNew(x,y,z){}

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

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

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

我想分享一个类似重载方法的有用示例。

function Clear(control){var o = typeof control !== "undefined" ? control : document.body;var children = o.childNodes;while (o.childNodes.length > 0)o.removeChild(o.firstChild);}

用法:清除(); // 清除所有文档

清除(myDiv);//清除myDiv引用的面板

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

/** 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"

我喜欢@AntouanK的方法。我经常发现自己提供一个具有不同数量o参数和不同类型的函数。有时他们不遵循顺序。我用来映射查找参数类型:

findUDPServers: function(socketProperties, success, error) {var fqnMap = [];
fqnMap['undefined'] = fqnMap['function'] = function(success, error) {var socketProperties = {name:'HELLO_SERVER'};
this.searchServers(socketProperties, success, error);};
fqnMap['object'] = function(socketProperties, success, error) {var _socketProperties = _.merge({name:'HELLO_SERVER'}, socketProperties || {});
this.searchServers(_socketProperties, success, error);};
fqnMap[typeof arguments[0]].apply(this, arguments);}

正确答案是JAVASCRIPT中没有超载。

函数内部的检查/切换不是超载。

重载的概念:在某些编程语言中,函数重载或方法重载是创建具有不同实现的多个同名方法的能力。对重载函数的调用将运行适合调用上下文的该函数的特定实现,允许一个函数调用根据上下文执行不同的任务。

举个例子,doTeam()和doTeam(对象O)是重载的方法。要调用后者,必须传递一个对象作为参数,而前者不需要参数,并且使用一个空参数字段调用。一个常见的错误是在第二个方法中为对象分配默认值,这将导致不明确的调用错误,因为编译器不知道使用这两个方法中的哪一个。

https://en.wikipedia.org/wiki/Function_overloading

所有建议的实现都很棒,但说实话,JavaScript没有原生实现。

#Forwarding Pattern=>JS重载的最佳实践转发到另一个函数,其名称是从第3和第4点构建的:

  1. 使用参数数
  2. 检查参数类型
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

#在您的案例中应用:

 function foo(...args){return window['foo_' + args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);
}//------Assuming that `x` , `y` and `z` are String when calling `foo` .  
/**-- for :  foo(x)*/function foo_1_string(){}/**-- for : foo(x,y,z) ---*/function foo_3_string_string_string(){      
}

#其他复杂样本:

      function foo(...args){return window['foo_'+args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);}
/** one argument & this argument is string */function foo_1_string(){
}//------------/** one argument & this argument is object */function foo_1_object(){
}//----------/** two arguments & those arguments are both string */function foo_2_string_string(){
}//--------/** Three arguments & those arguments are : id(number),name(string), callback(function) */function foo_3_number_string_function(){let args=arguments;new Person(args[0],args[1]).onReady(args[3]);}     
//--- And so on ....

JavaScript是无类型语言,我只认为在参数数量方面重载方法/函数是有意义的。因此,我建议检查参数是否已定义:

myFunction = function(a, b, c) {if (b === undefined && c === undefined ){// do x...}else {// do y...}};

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

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

另一种方法是使用特殊变量:参数,这是一个实现:

function sum() {var x = 0;for (var i = 0; i < arguments.length; ++i) {x += arguments[i];}return x;}

因此,您可以将此代码修改为:

function sum(){var s = 0;if (typeof arguments[0] !== "undefined") s += arguments[0];...return s;}

导言

到目前为止,阅读这么多答案会让任何人头疼。任何想知道这个概念的人都需要知道以下先决条件

Function overloading DefinitionFunction Length propertyFunction argument property

最简单的形式Function overloading表示函数根据传递给它的参数数量执行不同的任务。值得注意的是,下面突出显示了TASK1、TASK2和TASK3,它们是根据传递给同一个函数fooYoarguments的数量执行的。

// if we have a function defined belowfunction fooYo(){// do something here}// on invoking fooYo with different number of arguments it should be capable to do different things
fooYo();  // does TASK1fooYo('sagar'); // does TASK2fooYo('sagar','munjal'); // does TAKS3

注意-JS不提供函数重载的内置能力。

替代

John E Resig(JS的创建者)指出了一种替代方案,它使用上述先决条件来实现实现函数重载的能力。

下面的代码通过使用if-elseswitch语句使用了简单但天真的方法。

  • 计算argument-length属性。
  • 不同的值会导致调用不同的函数。

var ninja = {whatever: function() {switch (arguments.length) {case 0:/* do something */break;case 1:/* do something else */break;case 2:/* do yet something else */break;//and so on ...}}}

另一种技术更加干净和动态。这种技术的亮点是addMethod泛型函数。

  • 我们定义了一个函数addMethod,它用于向具有相同名称不同功能的对象添加不同的函数。

  • 下面的addMethod函数接受三个参数对象名称object、函数名称name和我们要调用的函数fn

  • 内部addMethod定义var old存储了对前一个function的引用,该引用是通过闭包的帮助存储的——一个保护气泡。

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下面添加了三个函数,当使用ninja.whatever(x)和参数x调用时,它们可以是任何东西,即空白或一个或多个调用定义的不同函数,同时使用addMethod函数。

var ninja = {};debugger;

addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });

ninja.whatever();ninja.whatever(1,2);ninja.whatever(3);

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

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

显然,您可以调整它以处理任意数量的参数,并相应地更改条件语句。

100行JS中通过动态多态性实现函数重载

这来自一个更大的代码体,其中包括isFnisArr等类型检查函数。下面的VanillaJS版本已被重新设计以删除所有外部依赖项,但是您必须定义自己的类型检查函数以用于.add()调用。

备注:这是一个自执行函数(因此我们可以有一个闭包/封闭范围),因此赋值给window.overload而不是function overload() {...}

window.overload = function () {"use strict"
var a_fnOverloads = [],_Object_prototype_toString = Object.prototype.toString;
function isFn(f) {return (_Object_prototype_toString.call(f) === '[object Function]');} //# isFn
function isObj(o) {return !!(o && o === Object(o));} //# isObj
function isArr(a) {return (_Object_prototype_toString.call(a) === '[object Array]');} //# isArr
function mkArr(a) {return Array.prototype.slice.call(a);} //# mkArr
function fnCall(fn, vContext, vArguments) {//# <ES5 Support for array-like objects//#     See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibilityvArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));
if (isFn(fn)) {return fn.apply(vContext || this, vArguments);}} //# fnCall
//#function registerAlias(fnOverload, fn, sAlias) {//#if (sAlias && !fnOverload[sAlias]) {fnOverload[sAlias] = fn;}} //# registerAlias
//#function overload(vOptions) {var oData = (isFn(vOptions) ?{ default: vOptions } :(isObj(vOptions) ?vOptions :{default: function (/*arguments*/) {throw "Overload not found for arguments: [" + mkArr(arguments) + "]";}})),fnOverload = function (/*arguments*/) {var oEntry, i, j,a = arguments,oArgumentTests = oData[a.length] || [];
//# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loopfor (i = 0; i < oArgumentTests.length; i++) {oEntry = oArgumentTests[i];
//# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loopfor (j = 0; j < a.length; j++) {if (!oArgumentTests[i].tests[j](a[j])) {oEntry = undefined;break;}}
//# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loopif (oEntry) {break;}}
//# If we found our oEntry above, .fn.call its .fnif (oEntry) {oEntry.calls++;return fnCall(oEntry.fn, this, a);}//# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .defaultelse {return fnCall(oData.default, this, a);}} //# fnOverload;
//#fnOverload.add = function (fn, a_vArgumentTests, sAlias) {var i,bValid = isFn(fn),iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0);
//#if (bValid) {//# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )for (i = 0; i < iLen; i++) {if (!isFn(a_vArgumentTests[i])) {bValid = _false;}}}
//# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLenif (bValid) {oData[iLen] = oData[iLen] || [];oData[iLen].push({fn: fn,tests: a_vArgumentTests,calls: 0});
//#registerAlias(fnOverload, fn, sAlias);
return fnOverload;}//# Else one of the passed arguments was not bValid, so throw the errorelse {throw "poly.overload: All tests must be functions or strings referencing `is.*`.";}}; //# overload*.add
//#fnOverload.list = function (iArgumentCount) {return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);}; //# overload*.list
//#a_fnOverloads.push(fnOverload);registerAlias(fnOverload, oData.default, "default");
return fnOverload;} //# overload
//#overload.is = function (fnTarget) {return (a_fnOverloads.indexOf(fnTarget) > -1);} //# overload.is
return overload;}();

用法:

调用者通过将变量分配给overload()的返回来定义他们的重载函数。由于链接,可以串联定义额外的重载:

var myOverloadedFn = overload(function(){ console.log("default", arguments) }).add(function(){ console.log("noArgs", arguments) }, [], "noArgs").add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str");

overload()的单个可选参数定义了在无法识别签名时要调用的“默认”函数。.add()的参数是:

  1. fnfunction定义重载;
  2. a_vArgumentTestsfunction中的Array定义了要在arguments上运行的测试。每个function接受一个参数,并根据参数是否有效返回truethy;
  3. sAlias(可选):string定义直接访问重载函数的别名(fn),例如myOverloadedFn.noArgs()将直接调用该函数,避免参数的动态多态测试。

这个实现实际上不仅仅允许传统的函数重载,因为在实践中.add()的第二个a_vArgumentTests参数定义了自定义类型。因此,您不仅可以根据类型来门控参数,还可以根据范围、值或值的集合来门控参数!

如果你查看overload()的145行代码,你会发现每个签名都是根据传递给它的arguments的数量进行分类的。这样做是为了限制我们正在运行的考试数量。我还跟踪调用计数。使用一些额外的代码,可以重新排序重载函数的数组,以便首先测试更常见的调用函数,再次添加一些性能增强措施。

现在,有一些警告……由于Javascript是松散类型的,你必须小心你的vArgumentTests,因为integer可以被验证为float,等等。

JSCompress.com版本(1114字节,744字节g压缩):

window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();

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

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.EventhandleObj = 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 );} );}

许多重载函数在针对性能进行优化时几乎无法读取。你必须破译函数的头部。这可能比使用我提供的overload函数更快;然而,从人类的角度来看,它在识别调用了哪个重载方面要慢得多。

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

function foo(var1, var2, opts){// set default values for parametersconst 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 bconsole.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/S传播属性(第4阶段)向对象文字添加传播属性。它将自己的可枚举属性从提供的对象复制到新对象上。更多关于mdn

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

JS中没有实际的重载,无论如何,我们仍然可以通过几种方式模拟方法重载:

方法#1:使用对象

function test(x,options){if("a" in options)doSomething();else if("b" in options)doSomethingElse();}test("ok",{a:1});test("ok",{b:"string"});

方法#2:使用rest(扩展)参数

function test(x,...p){if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")else if (p[1])console.log("2 params passed");else console.log("1 param passed");}

方法#3:使用未定义的

function test(x, y, z){if(typeof(z)=="undefined")doSomething();}

方法#4:类型检查

function test(x){if(typeof(x)=="string")console.log("a string passed")else ...}

可以对函数重载执行类似的操作。

function addCSS(el, prop, val) {return {2: function() {// when two arguments are set// now prop is an ojectfor (var i in prop) {el.style[i] = prop[i];}},3: function() {// when three arguments are setel.style[prop] = val;}}[arguments.length]();}// usagevar el = document.getElementById("demo");addCSS(el, "color", "blue");addCSS(el, {"backgroundColor": "black","padding": "10px"});

来源

JavaScript中的函数重载:

函数重载是一种编程语言用不同的实现创建多个同名函数的能力。当调用重载函数时,它将运行函数,该函数的特定实现适合调用的上下文。这个上下文通常是接收到的参数量,它允许一个函数调用根据上下文的不同表现不同。

Javascript不要具有内置函数重载。但是,可以通过多种方式模拟这种行为。这是一个方便简单的:

function sayHi(a, b) {console.log('hi there ' + a);if (b) { console.log('and ' + b) } // if the parameter is present, execute the block}
sayHi('Frank', 'Willem');

在你不知道你会得到多少参数的情况下,你可以使用休息操作符,它是三个点...。它将把其余的参数转换成一个数组。但要注意浏览器兼容性。这是一个例子:

function foo (a, ...b) {console.log(b);}
foo(1,2,3,4);foo(1,2);

虽然默认参数没有重载,但它可能会解决开发人员在这方面面临的一些问题。输入严格由顺序决定,你不能像经典重载那样随心所欲地重新排序:

function transformer(firstNumber = 1,secondNumber = new Date().getFullYear(),transform = function multiply(firstNumber, secondNumber) {return firstNumber * secondNumber;}) {return transform(firstNumber, secondNumber);}
console.info(transformer());console.info(transformer(8));console.info(transformer(2, 6));console.info(transformer(undefined, 65));
function add(firstNumber, secondNumber) {return firstNumber + secondNumber;}console.info(transformer(undefined, undefined, add));console.info(transformer(3, undefined, add));

结果(2020年):

202016160126520212023

更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

TypeScript手册提到了过载。虽然之前已经提到过检查类型和根据结果执行不同的逻辑,但读者可能会对这种定义多个命名函数以与类型系统一起工作的方法感兴趣。以下是TypeScript如何创建一个接受多种类型参数的函数,这些参数将根据传入的参数和类型指导函数逻辑执行不同的操作:

答案是为同一个函数提供多个函数类型作为重载列表。这个列表是编译器将用来解析函数调用的。让我们创建一个重载列表,描述我们的选择卡接受什么和它返回什么。

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 cardif (typeof x == "object") {let pickedCard = Math.floor(Math.random() * x.length);return pickedCard;}// Otherwise just let them pick the cardelse 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);

通过此更改,重载现在为我们提供了类型检查调用。

为了让编译器选择正确的类型检查,它遵循与底层JavaScript类似的过程。它查看重载列表,并继续第一个重载,尝试使用提供的参数调用函数。如果找到匹配项,它会选择此重载作为正确的重载。出于这个原因,习惯上将重载从最具体到最不具体排序。

需要注意的是,函数picCard(x): any不是重载列表的一部分,所以它只有两个重载:一个重载对象,另一个重载数字。

不是每个人都知道你可以在函数签名中直接执行破坏任务

因此,您可以轻松定义一个非常灵活的方法签名,即IMHO,优于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);});