JavaScript中的函数重载 – 最佳实践
在Javascript中伪造函数重载的最好方法是什么?
我知道这是不可能的在Javascript中重载函数,如其他语言。 如果我需要两个使用foo(x)
和foo(x,y,z)
这是最好的/首选的方法:
- 首先使用不同的名称
- 使用可选参数,如
y = y || 'default'
y = y || 'default'
- 使用参数数目
- 检查参数的类型
- 或者如何?
使用参数进行函数重载的最好方法不是检查参数长度或类型; 检查类型只会让你的代码变慢,而且你还可以获得数组,对象等的乐趣。
大多数开发者所做的就是将一个对象作为他们方法的最后一个参数。 这个对象可以容纳任何东西
function foo(a, b, opts) { } foo(1, 2, {"method":"add"}); foo(3, 4, {"test":"equals", "bar":"tree"});
然后你可以在你的方法中处理它。 [切换,如果其他等]
我经常这样做:
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 = one CatStrings("one",2); // result = one2 CatStrings("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 = one CatStrings("one",2); // result = one2 CatStrings("one",2,true); // result = one2true
这个特殊的例子实际上比C#更优雅。 未指定的参数在javascript中是“未定义的”,在if语句中它的计算结果为false。 但是,函数定义并不传达p2和p3是可选的信息。 如果你需要大量的重载,jQuery已经决定使用一个对象作为参数,例如jQuery.ajax(options)。 我同意他们这是超载的最强大和清晰的方法,但我很少需要超过一个或两个快速的可选参数。
编辑:改变IF测试每伊恩的建议
在JavaScript中没有真正的函数重载,因为它允许传递任何类型的任意数量的参数。 你必须在函数内部检查已经传递了多少个参数以及它们是什么类型。
有两种方法可以更好地处理这个问题:
-
如果你想留下很多的灵活性,传递一个字典(关联数组)
-
以一个对象作为参数,并使用基于原型的继承来增加灵活性。
正确的答案是在JavaScript中没有超载。
在函数内部检查/切换不是OVERLOADING。
重载的概念:在一些编程语言中,函数重载或方法重载是能够用不同的实现方式创建同名的多个方法。 对重载函数的调用将运行适合于调用上下文的该函数的特定实现,允许一个函数调用根据上下文执行不同的任务。
例如,doTask()和doTask(对象O)是重载的方法。 要调用后者,必须将一个对象作为参数传递,而前者不需要参数,并用空的参数字段调用。 一个常见的错误是为第二个方法中的对象分配一个默认值,这会导致模糊的调用错误,因为编译器不知道使用哪种方法。
https://en.wikipedia.org/wiki/Function_overloading
所有建议的实现都很好,但是事实上,JavaScript没有本地实现。
这是一个关于函数重载的基准测试 – http://goo.gl/UyYAD (在这篇文章中显示的代码)。 它表明,功能过载(考虑到类型)可能会比 Google Chrome的V8 慢16倍 (测试版) 13倍左右。
除了传递一个对象(即{x: 0, y: 0}
)外,还可以在适当的时候采用C方法,相应地命名方法。 例如,Vector.AddVector(vector),Vector.AddIntegers(x,y,z,…)和Vector.AddArray(integerArray)。 你可以看看C库,比如OpenGL来命名灵感。
编辑 :我已经添加了一个基准,传递一个对象,并'param' in arg
和arg.hasOwnProperty('param')
使用'param' in arg
来测试对象,而函数重载比传递一个对象和检查属性(在这个基准至少)。
从设计角度来看,如果重载的参数对应于相同的动作,则函数重载只是有效的或逻辑的。 所以有理由认为应该有一个只涉及具体细节的底层方法,否则可能表明不适当的设计选择。 所以也可以通过将数据转换为相应的对象来解决函数重载的使用。 当然,我们必须考虑问题的范围,因为如果你的意图是打印一个名字,那么就不需要进行精心的设计,但是对于框架和图书馆的设计来说,这样的思想是合理的。
我的例子来自一个Rectangle实现 – 因此提到了Dimension和Point。 也许Rectangle可以将一个GetRectangle()
方法添加到Dimension
和Point
原型中,然后对函数重载问题进行排序。 那么基元呢? 那么,我们有参数长度,现在是一个有效的测试,因为对象有一个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);
最好的方法确实取决于函数和参数。 在不同的情况下,您的每个选项都是一个好主意。 我通常按以下顺序尝试这些,直到其中一个工作:
-
使用可选参数,如y = y || '默认'。 如果你可以这样做,这很方便,但是它可能并不总是实际工作,例如,当0 / null / undefined将是一个有效的参数。
-
使用参数数目。 类似于最后一个选项,但当#1不起作用时可能工作。
-
检查参数的类型。 这可以在一些参数数量相同的情况下工作。 如果您无法可靠地确定类型,则可能需要使用不同的名称。
-
首先使用不同的名称。 如果其他选项不起作用,不实用或与其他相关功能保持一致,则可能需要执行此操作。
如果我需要一个函数有两个用途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
语句。
( 更多细节 )
我不确定最佳做法,但是我是这么做的:
/* * 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"
我只是试过这个,也许它适合你的需求。 根据参数的数量,您可以访问不同的功能。 你第一次调用它时初始化它。 功能图隐藏在闭包中。
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; };
测试它。
TEST.multiFn("0") .multiFn() .multiFn("0","1","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 + "}"; }
这里是一个关于如何使用options对象的例子
在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!!"); } }
祝你好运!
看一下这个。 这很酷。 http://ejohn.org/blog/javascript-method-overloading/哄骗JavaScript,让你做这样的电话:;
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
由于这篇文章已经包含了很多不同的解决方案,我想我张贴另一个。
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));
这个解决方案并不完美,但我只想演示如何完成。
你可以使用John Resig的'addMethod'。 有了这个方法,你可以根据参数计数“重载”方法。
// 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 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); }; }
这是一个古老的问题,但我认为需要另一个条目(虽然我怀疑有人会读它)。 立即调用函数表达式(IIFE)的使用可以与闭包和内联函数结合使用,以实现函数重载。 考虑以下(人为的)例子:
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的使用创建了一个局部范围,允许我们定义私有变量old
以存储对函数foo
的初始定义的引用。 这个函数然后返回一个内联函数newFoo
,它记录两个参数的内容,如果它恰好传递了两个参数a
和b
或者调用old
函数if arguments.length !== 2
。 这种模式可以重复任意次数赋予一个变量几个不同的功能定义。
转发模式=> JS重载的最佳实践
转到第三和第四点构建的另一个函数:
- 使用参数数目
- 检查参数的类型
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)
申请你的情况:
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(){ }
其他复杂样本:
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 ....
JavaScript是无类型的语言,我只认为在params的数量方面重载一个方法/函数是有意义的。 因此,我会建议检查参数是否已被定义:
myFunction = function(a, b, c) { if (b === undefined && c === undefined ){ // do x... } else { // do y... } };
截至2017年7月,以下是常用技术。 请注意,我们也可以在函数中执行类型检查。
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
解决这个问题的另一种方法是使用特殊变量: arguments ,这是一个实现:
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] ; if(typeof arguments[0] !== "undefined") s +=arguments[0] ; . . . return s; }
介绍
到目前为止,阅读这么多的答案会给任何人一个头痛的问题。 任何试图了解这个概念的人都需要知道以下的先决条件 。
Function overloading Definition
, Function Length property
, Function argument property
以最简单的形式Function overloading
意味着函数根据传递给它的参数的数量执行不同的任务。 值得注意的是TASK1,TASK2和TASK3在下面突出显示,并根据传递给相同函数fooYo
的arguments
数目fooYo
。
// 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不提供内置的功能重载能力。
替代
JS的创建者John E Resig指出了一个替代方案,它使用上述先决条件来实现实现函数重载的能力。
下面的代码通过使用if-else
或switch
语句使用一种简单但天真的方法。
- 评估
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 ... } } }
第一个选项真的值得关注,因为这是我在相当复杂的代码设置中出现的东西。 所以,我的答案是
- 首先使用不同的名称
有一点点,但重要的提示,名称应该看起来不同的电脑,但不适合你。 名称重载的函数,如:func,func1,func2。
我们做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忍者的秘密
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语句,如果你开始编写复杂的代码(接受的答案会导致这种情况),就很难阅读。
在缺点方面,我猜这个代码最初是有点模糊的,但是我不确定其他的呢?
我想分享一个重载的方法的有用的例子。
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(); //清除所有的文件
清除(myDiv); //清除由myDiv引用的面板
I like @AntouanK's approach. I often find myself offering a function with different numbers o parameters and different types. Sometimes they don't follow a order. I use to map looking the types of parameters:
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); }
For your use case, this is how I would tackle it with ES6
(since it's already the end of 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; }
You can obviously adapt this to work with any amount of parameters and just change your conditional statements accordingly.
I am working on a library that provides class like code capabilities to Javascript, currently it supports constructors, inheritance, methods overload by number of params and by types of params, mixins, statics properties and singleton.
See an example of method overloading using that library:
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
Any feedback, bug fixes, docs and tests improves are welcome!