Function overloading in Javascript - Best practices
在javascript中伪造函数重载的最佳方法是什么?
我知道不可能像其他语言那样重载JavaScript中的函数。如果我需要一个函数,它有两种用法:
使用参数进行函数重载的最佳方法是不要检查参数长度或类型;检查类型只会使代码变慢,并且可以享受数组、空值、对象等的乐趣。
大多数开发人员所做的就是将对象作为方法的最后一个参数。这个物体可以容纳任何东西。
1 2 3 4 5 6 7 8 | 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"}); |
然后你可以在你的方法中任意处理它。[如有,请切换]
我经常这样做:
C:
1 2 3 4 5 6 7 | 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 = one CatStrings("one",2); // result = one2 CatStrings("one",2,true); // result = one2true |
javascript等价物:
1 2 3 4 5 6 7 8 9 10 11 | 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 = one CatStrings("one",2); // result = one2 CatStrings("one",2,true); // result = one2true |
这个特殊的例子在JavaScript中实际上比C更优雅。未指定的参数在javascript中是"未定义"的,在if语句中计算结果为false。但是,函数定义没有传递p2和p3是可选的信息。如果需要大量重载,jquery决定使用对象作为参数,例如jquery.ajax(选项)。我同意他们的观点,这是处理过载的最强大和清晰的文档化方法,但我很少需要一个或两个以上的快速可选参数。
编辑:如果根据伊恩的建议进行测试,则更改
JavaScript中没有真正的函数重载,因为它允许传递任意类型的参数。您必须在函数内部检查传递了多少参数以及它们是什么类型的。
正确的答案是JavaScript中没有重载。
功能内部的检查/切换没有过载。
过载的概念:在某些编程语言中,函数重载或方法重载是用不同的实现创建多个同名方法的能力。对重载函数的调用将根据调用的上下文运行该函数的特定实现,允许一个函数调用根据上下文执行不同的任务。
例如,dotask()和dotask(object o)是重载方法。要调用后者,必须将对象作为参数传递,而前者不需要参数,而是使用空参数字段调用。一个常见的错误是为第二个方法中的对象分配一个默认值,这将导致一个不明确的调用错误,因为编译器不知道要使用哪两个方法。
https://en.wikipedia.org/wiki/function_重载
所有建议的实现都很好,但说实话,没有针对javascript的本地实现。
有两种方法可以更好地实现这一点:
如果你想保持很大的灵活性,就传递一个字典(关联数组)
以一个对象为参数,使用基于原型的继承来增加灵活性。
下面是一种允许使用参数类型进行实际方法重载的方法,如下所示:
1 2 3 4 | Func(new Point()); Func(new Dimension()); Func(new Dimension(), new Point()); Func(0, 0, 0, 0); |
编辑(2018):自2011年编写以来,直接方法调用的速度大大提高,而重载方法的速度却没有提高。
这不是我推荐的方法,但是思考如何解决这些类型的问题是一个值得思考的练习。
这里是不同方法的基准-https://jspef.com/function-overloading。从16.0版(beta版)开始,谷歌Chrome的V8中的函数过载(考虑到类型)可能要慢13倍左右。
除了传递对象(即
编辑:我添加了一个通过对象和使用
从设计的角度来看,只有当重载的参数对应于相同的操作时,函数重载才是有效的或逻辑的。因此,有理由认为应该有一个只涉及特定细节的底层方法,否则可能表明不适当的设计选择。因此,还可以通过将数据转换为相应的对象来解决函数重载的使用问题。当然,我们必须考虑问题的范围,因为如果您只想打印一个名称,就不需要进行精心设计,但是对于框架和库的设计,这种想法是合理的。
我的示例来自一个矩形实现——因此提到了维度和点。也许矩形可以在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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); |
最好的方法实际上取决于函数和参数。在不同的情况下,每个选项都是一个好主意。我通常按以下顺序尝试这些方法,直到其中一个有效:
使用可选参数,如y=y"default"。如果可以这样做的话,这很方便,但实际上可能并不总是有效的,例如,当0/null/undefined是有效参数时。
使用参数个数。与上一个选项类似,但可能在1不工作时工作。
正在检查参数类型。在一些参数数目相同的情况下,这可以工作。如果不能可靠地确定类型,则可能需要使用不同的名称。
首先使用不同的名称。如果其他选项不起作用、不实用或与其他相关功能一致,则可能需要执行此操作。
If I needed a function with two uses foo(x) and foo(x,y,z) which is the best / preferred way?
问题是JavaScript本身不支持方法重载。因此,如果它看到/解析具有相同名称的两个或多个函数,它将只考虑最后一个定义的函数并覆盖前面的函数。
我认为适合大多数情况的方法之一是-
假设你有方法
1 2 3 | function foo(x) { } |
您可以定义一个新的方法,而不是在javascript中无法实现的重载方法。
1 2 3 | fooNew(x,y,z) { } |
然后修改第一个函数如下-
1 2 3 4 5 6 7 | function foo(arguments) { if(arguments.length==2) { return fooNew(arguments[0], arguments[1]); } } |
如果您有许多这样的重载方法,请考虑使用
(更多细节)
附:上面的链接指向我的个人博客,上面有更多的细节。
我不确定最佳实践,但我是这样做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | /* * 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" |
我刚试过这个,也许它适合你的需要。根据参数的数量,您可以访问不同的函数。第一次调用它时初始化它。函数映射隐藏在闭包中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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; }; |
测试它。
1 2 3 | TEST.multiFn("0") .multiFn() .multiFn("0","1","2"); |
因为javascript没有函数重载选项,所以可以使用对象。如果有一个或两个必需的参数,最好将它们与Options对象分开。下面是一个示例,说明如何在选项对象中未传递值的情况下,将选项对象和填充值使用为默认值。
1 2 3 4 5 6 7 8 9 10 | 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 +"}"; } |
下面是一个关于如何使用选项对象的示例
另一种方法是使用特殊变量:arguments,这是一个实现:
1 2 3 4 5 6 7 | function sum() { var x = 0; for (var i = 0; i < arguments.length; ++i) { x += arguments[i]; } return x; } |
因此,您可以将此代码修改为:
1 2 3 4 5 6 7 8 | function sum(){ var s = 0; if (typeof arguments[0] !=="undefined") s += arguments[0]; . . . return s; } |
介绍
到目前为止,通读这么多答案会让人头疼。任何想了解这个概念的人都需要了解以下先决条件。
1 2 3 4 5 6 7 8 9 | // if we have a function defined below function fooYo(){ // do something here } // on invoking fooYo with different number of arguments it should be capable to do different things fooYo(); // does TASK1 fooYo('sagar'); // does TASK2 fooYo('sagar','munjal'); // does TAKS3 |
注释-JS不提供函数重载的内置功能。
替代方案
John E Resig(JS的创建者)指出了一种替代方法,它使用上述前提条件来实现实现函数重载的能力。
下面的代码通过使用
- 评估
argument-length 属性。 - 不同的值导致调用不同的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 函数下面接受三个参数:对象名object 、函数名name 和要调用的函数fn 。- 在
addMethod 定义中,var old 存储了对前一个function 的引用,该引用是通过闭合(一个保护性气泡)来存储的。
1 2 3 4 5 6 7 8 9 | 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 函数时,可以是空白的或一个或多个调用不同的函数。
1 2 3 4 5 6 7 8 9 10 11 12 | 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); |
在javascript中没有方法进行函数重载。因此,我建议用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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!!"); } } |
祝你好运!
看看这个。很酷。http://ejohn.org/blog/javascript-method-overloading/使用Javascript进行如下调用:
1 2 3 4 | var users = new Users(); users.find(); // Finds all users.find("John"); // Finds users by name users.find("John","Resig"); // Finds users by first and last name |
因为这篇文章已经包含了很多不同的解决方案,所以我想我会发布另一个解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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"); } |
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)); |
这个解决方案并不完美,但我只想演示如何做到。
转发模式=>JS重载的最佳实践
转发到另一个从第3点和第4点构建的函数:
Using number of arguments Checking types of arguments
1 | window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments) |
您的案件申请:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function foo(){ return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments); } //------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(){ } |
其他复杂样品:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function foo(){ return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments); } /** 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 .... |
您现在可以在EcmaScript 2018中进行函数重载,而不需要使用polyfill、检查var长度/类型等,只需使用spread语法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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" |
The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.
More on mdn
注意:对象文本中的扩展语法在EDGE和IE中不起作用,它是一个实验特性。请参见浏览器兼容性
基于动态多态的100行JS函数重载
- VanillaJS,无外部依赖
- 全浏览器支持-array.prototype.slice,object.prototype.toString
- 1114字节uglify'd/744字节g-zipped
这是一个更大的代码体,其中包括
注:这是一个自执行功能(因此我们可以有一个关闭/关闭范围),因此分配给
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | 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_compatibility vArguments = (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 loop for (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 loop for (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 loop if (oEntry) { break; } } //# If we found our oEntry above, .fn.call its .fn if (oEntry) { oEntry.calls++; return fnCall(oEntry.fn, this, a); } //# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default else { 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 iLen if (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 error else { 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; }(); |
用途:
调用者通过给
1 2 3 4 | 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") ; |
这种实现实际上允许的不仅仅是传统的函数重载,因为实际中
如果您查看
现在,有一些警告……由于Javascript是松散类型的,因此您必须小心使用
jscompress.com版本(1114字节,744字节g-zipped):
1 | 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}(); |
您可以从john resig中使用"addmethod"。使用此方法,可以基于参数计数"重载"方法。
1 2 3 4 5 6 7 8 9 10 | // 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 ); }; } |
我还创建了一个替代方法来使用缓存来保存函数的变化。这里描述了不同之处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // addMethod - By Stavros Ioannidis function addMethod(obj, name, fn) { obj[name] = obj[name] || function() { // get the cached method with arguments.length arguments var method = obj[name].cache[arguments.length]; // if method exists call it if ( !! method) return method.apply(this, arguments); else throw new Error("Wrong number of arguments"); }; // initialize obj[name].cache obj[name].cache = obj[name].cache || {}; // Check if a method with the same number of arguments exists if ( !! 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 arguments obj[name].cache[fn.length] = function() { return fn.apply(this, arguments); }; } |
对于您的用例,这就是我将如何使用
1 2 3 4 5 6 7 8 | const foo = (x, y, z) => { if (y && z) { // Do your foo(x, y, z); functionality return output; } // Do your foo(x); functionality return output; } |
显然,您可以将其调整为使用任意数量的参数,并相应地更改条件语句。
截至2017年7月,以下是常用的技术。注意,我们也可以在函数中执行类型检查。
1 2 3 4 5 | 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 |
这是一个旧问题,但我认为需要另一个条目(尽管我怀疑任何人都会读)。可以将立即调用的函数表达式(IIFE)与闭包和内联函数一起使用,以允许函数重载。请考虑以下(人为)示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | var foo; // original 'foo' definition foo = function(a) { console.log("a:" + a); } // define 'foo' to accept two arguments foo = (function() { // store a reference to the previous definition of 'foo' var old = foo; // use inline function so that you can refer to it internally return 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: 1 foo(1,2); > a: 1 > b: 2 foo(1,2,3) > a: 1 |
简而言之,IIFE的使用创建了一个局部范围,允许我们定义私有变量
第一个选项确实值得注意,因为它是我在非常复杂的代码设置中提出的。所以,我的答案是
Using different names in the first place
有了一点基本的提示,计算机的名字应该看起来不一样,但对你来说不一样。名称重载函数,如:func、func1、func2。
javascript是非类型化的语言,我只认为根据参数的数量重载方法/函数是有意义的。因此,我建议检查参数是否已定义:
1 2 3 4 5 6 7 8 | myFunction = function(a, b, c) { if (b === undefined && c === undefined ){ // do x... } else { // do y... } }; |
我们通过.js来解决这个问题是一种非常优雅的方式。你可以做到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); } ) }; |
我喜欢@antouank的方法。我经常发现自己提供的函数具有不同的数字o参数和不同的类型。有时他们不服从命令。我用来映射参数类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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忍者秘籍中发现的事情。
1 2 3 4 5 6 7 8 9 10 | 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==arguments.length——这很有效,因为fn.length是预期参数的数量,而arguments.length是用函数实际调用的参数的数量。匿名函数没有参数的原因是,您可以用JavaScript传递任意数量的参数,并且该语言可以被原谅。
我喜欢这个,因为你可以在任何地方使用它——只需创建这个函数,并在任何你想要的代码库中简单地使用这个方法。
它还避免了一个非常大的if/switch语句,如果您开始编写复杂的代码,这将变得难以读取(接受的答案将导致这种情况)。
在缺点方面,我猜代码最初有点模糊……但我不确定其他人?
我想分享一个类似于重载的方法的有用示例。
1 2 3 4 5 6 7 | function Clear(control) { var o = typeof control !=="undefined" ? control : document.body; var children = o.childNodes; while (o.childNodes.length > 0) o.removeChild(o.firstChild); } |
用途:clear();//清除所有文档
clear(mydiv);//清除mydiv引用的面板
多年来,我一直使用此函数来美化我的重载:
1 2 3 4 5 6 7 | 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); } } |
Demostrated:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 ); } ); } |
许多为性能优化而重载的函数几乎是不可读的。你必须破译函数的头部。这可能比使用我提供的
对于函数重载,可以这样做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function addCSS(el, prop, val) { return { 2: function() { // when two arguments are set // now prop is an oject for (var i in prop) { el.style[i] = prop[i]; } }, 3: function() { // when three arguments are set el.style[prop] = val; } }[arguments.length](); } // usage var el = document.getElementById("demo"); addCSS(el,"color","blue"); addCSS(el, { "backgroundColor":"black", "padding":"10px" }); |
来源
JS中没有实际的重载,但是我们仍然可以通过几种方式模拟方法重载:
方法1:使用对象
1 2 3 4 5 6 | function test(x,options){ if("a" in options)doSomething(); else if("b" in options)doSomethingElse(); } test("ok",{a:1}); test("ok",{b:"string"}); |
方法2:使用静止(排列)参数
1 2 3 4 5 | 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:未定义使用
1 2 3 | function test(x, y, z){ if(typeof(z)=="undefined")doSomething(); } |
方法4:类型检查
1 2 3 4 | function test(x){ if(typeof(x)=="string")console.log("a string passed") else ... } |
我正在开发一个库,它为javascript提供类代码功能,目前它支持构造函数、继承、按参数数量和参数类型重载方法、混合、静态属性和单例。
请参阅使用该库的方法重载示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 object test.value(); // will return '5, 2' test.sum(); // will return 7 test.value(13); // will set x to 13 test.value(); // will return '13, 2' test.sum(); // will return 15 test.value(10, 20); // will set x to 10 and y to 20 test.value(); // will return '10, 20' test.sum(); // will return 30 |
欢迎任何反馈、错误修复、文档和测试改进!
https://github.com/eutsiv/eutsiv.js网站