如何在javascript中重载函数?
古典(非JS)方法来重载:
function myFunc(){ //code } function myFunc(overloaded){ //other code }
JavaScript不会让多个函数被定义为相同的名称。 因此,这样的事情显示出来:
function myFunc(options){ if(options["overloaded"]){ //code } }
有没有更好的解决方法,在javascript中的函数重载,而不是传递一个对象与重载在它?
传入重载可以很快导致函数变得太冗长,因为每个可能的重载都需要一个条件语句。 使用函数来完成这些条件语句中的//code
可能会导致一些棘手的情况。
在Javascript中有很多方面的参数重载:
-
variables参数 – 您可以传递不同的参数集(types和数量),函数的行为将与传递给它的参数匹配。
-
默认参数 – 如果参数未被传递,您可以为参数定义默认值。
-
命名参数 – 参数顺序变得无关紧要,您只需指定要传递给函数的参数。
以下是关于这些参数处理类别的部分。
variables参数
因为javascript没有对参数或所需数量的参数进行types检查,所以可以通过检查参数的types,存在或数量来实现myFunc()
一个实现,该实现可以适应传递给它的参数。
jQuery一直这样做。 你可以使一些参数是可选的,或者你可以在你的函数中进行分支,这取决于传递给它的参数。
在实现这些types的重载时,可以使用几种不同的技术:
- 您可以通过检查声明的参数名称值是否
undefined
来检查是否存在任何给定的参数。 - 您可以使用
arguments.length
检查总数量或arguments.length
。 - 你可以检查任何给定参数的types。
- 对于可变数量的参数,可以使用
arguments
pseudo-array访问带有arguments[i]
任何给定参数。
这里有些例子:
我们来看看jQuery的obj.data()
方法。 它支持四种不同的使用forms:
obj.data("key"); obj.data("key", value); obj.data(); obj.data(object);
每一个触发不同的行为,而不使用这种dynamic的超载forms,将需要四个独立的function。
下面介绍一下如何在英文中识别所有这些选项,然后将所有这些选项合并到代码中:
// get the data element associated with a particular key value obj.data("key");
如果传递给.data()
的第一个参数是一个string,第二个参数是undefined
,那么调用者必须使用这个表单。
// set the value associated with a particular key obj.data("key", value);
如果第二个参数不是未定义的,那么设置一个特定键的值。
// get all keys/values obj.data();
如果没有parameter passing,则返回返回对象中的所有键/值。
// set all keys/values from the passed in object obj.data(object);
如果第一个参数的types是普通对象,那么设置该对象的所有键/值。
以下是如何将所有这些内容组合到一个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 } },
这个技巧的关键是要确保你想要接受的所有forms的参数都是唯一可识别的,而且从来没有任何关于调用者使用哪种forms的混淆。 这通常需要对参数进行适当sorting,并确保参数的types和位置具有足够的唯一性,以便始终可以确定正在使用哪种forms。
例如,如果您有一个需要三个string参数的函数:
obj.query("firstArg", "secondArg", "thirdArg");
你可以很容易地使第三个参数是可选的,你可以很容易地检测到这个条件,但是你不能只让第二个参数是可选的,因为你不能告诉哪个调用者意味着要通过,因为没有办法确定第二个参数参数是第二个参数,第二个参数是省略的,所以第二个参数的地方是第三个参数:
obj.query("firstArg", "secondArg"); obj.query("firstArg", "thirdArg");
由于所有三个参数都是相同的types,所以你不能分辨不同的参数之间的区别,所以你不知道调用者的意图。 使用这种调用方式,只有第三个参数是可选的。 如果你想省略第二个参数,它将不得不作为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的例子,其中的参数可能会丢失或三种不同types中的任何一种给你四种不同的重载:
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不直接支持命名参数的function。 一个常用的devise模式是传递一个属性/值的映射。 这可以通过传递一个具有属性和值的对象来实现,或者在ES6及其以上版本中实际传递一个Map对象本身。
这里有一个简单的ES5例子:
jQuery的$.ajax()
接受一种使用forms,在这里只传递一个参数,这个参数是一个具有属性和值的常规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中非常常用。 注意:这会对实现函数负责,以确保提供最less的有效参数集合,并为调用者提供一些debugging反馈,如果传递的参数不足,可能会丢失什么(可能是通过抛出一个带有有用错误消息的exception) 。
在ES6环境中,可以使用解构为上面传递的对象创build默认属性/值。 这在这篇参考文章中有更详细的讨论。
这篇文章中有一个例子:
function selectEntries({ start=0, end=-1, step=1 } = {}) { ··· };
这将为传递给selectEntries()
函数的对象创buildstart
, end
和step
属性的默认属性和值。
函数参数的默认值
在ES6中,Javascript为参数的默认值添加了内置的语言支持。
例如:
function multiply(a, b = 1) { return a*b; } multiply(5); // 5
关于在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
,如果有人将2个以上的parameter passing给foo
,它会失败,所以更好的forms是使用模块模式和一些检查:
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; }());
当然,你的重载可能需要使用dynamic数量的参数,所以你可以使用一个对象的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
,虽然它大容量的主function。 我将使用这种技术的一个常见例子是一个访问器/增变器方法:
function Foo() {} //constructor Foo.prototype = { bar: function (val) { switch (arguments.length) { case 0: return this._bar; case 1: this._bar = val; return this; } } }
我正在使用基于参数数量的一些不同的重载方法。 不过我相信约翰·福西特的做法也不错。 这里的例子是基于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 :)
严格意义上你不能做方法重载。 不像它在java
或c#
支持的方式。
问题是JavaScript本身不支持方法重载。 因此,如果它看到/分析具有相同名称的两个或多个函数,它将只考虑最后定义的函数并覆盖前面的函数。
我认为适合大部分情况的方式之一是遵循 –
可以说你有方法
function foo(x) { }
而不是JavaScript中不可能的重载方法,你可以定义一个新的方法
fooNew(x,y,z) { }
然后修改第一个function如下 –
function foo(x) { if(arguments.length==2) { return fooNew(arguments[0], arguments[1]); } }
如果你有很多这样的重载方法,请考虑使用switch
而不仅仅是if-else
语句。
( 更多细节 )
我试图开发一个优雅的解决scheme来解决这个问题。 你可以在这里find演示。 用法如下所示:
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; };
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"
在JavaScript中,您可以只实现一次函数,并调用没有参数的函数myFunc()
然后检查是否选项是'未定义'
function myFunc(options){ if(typeof options != 'undefined'){ //code } }
bob.js框架有一个不同的机制来支持重载。 这包括定义重载的函数体以及定义区分重载的条件:
var notify = new bob.fn.overloadFunction([ { condition: function(msg) { return bob.utils.isString(msg); }, overload: function(msg) { console.log(msg); } }, { condition: function(bSayHello) { return bob.utils.isBoolean(bSayHello); }, overload: function(bSayHello, msg) { msg = bSayHello ? 'Hello: ' + msg : msg; console.log(msg); } } ]);
然而,最终的结果是相当干净的:
notify('Simple sentence.'); // Output: // Simple sentence. notify(true, 'Greeting sentence.'); // Output: // Hello: Greeting sentence. notify(123); // JavaScript Error: // "No matching overload found."
看一下这个:
http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions
基本上在你的类中,你为你想要被重载的函数编号,然后用一个函数调用你添加函数重载,快速而简单。
由于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对象的例子
为此,您需要创build一个将函数添加到对象的函数,然后根据发送给函数的参数数量执行该函数:
<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>
在JS中重载没有问题,pb如何在重载函数时保持干净的代码?
你可以使用一个转发来干净的代码,基于两件事情:
- 参数个数(调用函数时)。
-
参数types(当在函数中调用时)
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的参数组的能力。
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; };
你试图达到的最好的做法是使用函数的局部参数variables。
function foo() { if (arguments.length === 0) { //do something } if (arguments.length === 1) { //do something else } } foo(); //do something foo('one'); //do something else
你可以在这里find更好的解释。