在 Javascript 中伪造函数重载的最佳方法是什么?
我知道不可能像在其他语言中那样重载 Javascript 中的函数。如果我需要一个具有两种用途的功能,foo(x)
并且foo(x,y,z)
这是最好/首选的方式:
- 首先使用不同的名称
- 使用可选参数,如
y = y || 'default'
- 使用参数数量
- 检查参数的类型
- 或者怎么做?
在 Javascript 中伪造函数重载的最佳方法是什么?
我知道不可能像在其他语言中那样重载 Javascript 中的函数。如果我需要一个具有两种用途的功能,foo(x)
并且foo(x,y,z)
这是最好/首选的方式:
y = y || 'default'
使用参数进行函数重载的最好方法是不检查参数长度或类型;检查类型只会让你的代码变慢,你会享受到数组、空值、对象等的乐趣。
大多数开发人员所做的是将对象作为其方法的最后一个参数。这个对象可以容纳任何东西。
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"});
然后你可以用你的方法来处理它。[开关、if-else 等]
我经常这样做:
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
这个特定的例子实际上在 javascript 中比在 C# 中更优雅。未指定的参数在 javascript 中为“未定义”,在 if 语句中计算为 false。但是,函数定义没有传达 p2 和 p3 是可选的信息。如果需要大量重载,jQuery 决定使用一个对象作为参数,例如 jQuery.ajax(options)。我同意他们的观点,这是最强大且可清楚记录的重载方法,但我很少需要超过一两个快速可选参数。
编辑:根据 Ian 的建议更改了 IF 测试
JavaScript 中没有真正的函数重载,因为它允许传递任意数量的任意类型的参数。您必须在函数内部检查传递了多少参数以及它们是什么类型。
正确答案是 JAVASCRIPT 没有重载。
函数内部的检查/切换不是过载。
重载的概念:在某些编程语言中,函数重载或方法重载是能够创建多个具有不同实现的同名方法。对重载函数的调用将运行适合于调用上下文的该函数的特定实现,允许一个函数调用根据上下文执行不同的任务。
例如,doTask() 和 doTask(object O) 是重载方法。要调用后者,必须将对象作为参数传递,而前者不需要参数,并且使用空参数字段调用。一个常见的错误是在第二种方法中为对象分配默认值,这将导致不明确的调用错误,因为编译器不知道要使用这两种方法中的哪一种。
https://en.wikipedia.org/wiki/Function_overloading
所有建议的实现都很棒,但说实话,JavaScript 没有原生实现。
有两种方法可以更好地解决这个问题:
如果您想保留很大的灵活性,请传递字典(关联数组)
将对象作为参数并使用基于原型的继承来增加灵活性。
这是一种允许使用参数类型进行真实方法重载的方法,如下所示:
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)开始,Google Chrome 的 V8中的函数重载(考虑到类型)可能会慢 13 倍左右。
除了传递一个对象(即{x: 0, y: 0}
),还可以在适当的时候采用 C 方法,相应地命名方法。例如,Vector.AddVector(vector)、Vector.AddIntegers(x, y, z, ...) 和 Vector.AddArray(integerArray)。您可以查看 C 库,例如 OpenGL,以获得命名灵感。
编辑:我添加了一个用于传递对象并使用 and 测试对象的基准'param' in arg
,arg.hasOwnProperty('param')
并且函数重载比传递对象和检查属性要快得多(至少在这个基准中)。
从设计的角度来看,函数重载只有在重载的参数对应于相同的动作时才有效或合乎逻辑。所以理所当然地应该有一个只关注特定细节的底层方法,否则可能表明设计选择不合适。因此,还可以通过将数据转换为相应的对象来解决函数重载的问题。当然,必须考虑问题的范围,因为如果您的意图只是打印一个名称,则无需进行精心设计,但是对于框架和库的设计,这种想法是合理的。
我的例子来自一个 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");
不是每个人都知道您可以直接在函数签名中进行解构赋值。
多亏了这一点,您可以轻松定义一个非常灵活的方法签名,即恕我直言,优于 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);
});
由于 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 + "}";
}
这是一个关于如何使用选项对象的示例
介绍
到目前为止,阅读这么多答案会让任何人头疼。任何想了解这个概念的人都需要知道以下先决条件。
Function overloading Definition
, Function Length property
,Function argument property
Function overloading
最简单的形式意味着函数根据传递给它的参数数量执行不同的任务。值得注意的是 TASK1、TASK2 和 TASK3 在下面突出显示,它们是根据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 不提供函数重载的内置能力。
选择
John E Resig(JS 的创建者)指出了一个替代方案,它使用上述先决条件来实现实现函数重载的能力。
if-else
下面的代码通过 using orswitch
语句使用了一种简单但幼稚的方法。
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);
没有办法在 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!!");
}
}
祝你好运!
另一种解决方法是使用特殊变量: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];
.
.
.
return s;
}
看一下这个。这很酷。 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
#Forwarding Pattern => JS 重载转发到另一个函数的最佳实践,该函数的名称是从第 3 点和第 4 点构建的:
- 使用参数数量
- 检查参数的类型
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 ....
由于这篇文章已经包含很多不同的解决方案,我想我会发布另一个。
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);
};
}
这是来自更大的代码主体,其中包括isFn
,isArr
等类型检查功能。下面的 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_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;
}();
调用者通过将变量分配给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()
是:
fn
:function
定义重载;a_vArgumentTests
: Array
of function
s 定义要在arguments
. 每个都function
接受一个参数并true
根据参数是否有效返回 thy;sAlias
(可选):string
定义别名以直接访问重载函数(fn
),例如myOverloadedFn.noArgs()
将直接调用该函数,避免参数的动态多态性测试。这个实现实际上允许的不仅仅是传统的函数重载,因为在实践中定义自定义类型的第二个a_vArgumentTests
参数。.add()
因此,您不仅可以基于类型,还可以基于范围、值或值集合来对参数进行门控!
如果您查看 145 行代码,overload()
您会发现每个签名都按arguments
传递给它的数量进行分类。这样做是为了限制我们正在运行的测试数量。我还跟踪呼叫计数。通过一些额外的代码,重载函数的数组可以重新排序,以便首先测试更常用的函数,再次添加一些性能增强措施。
现在,有一些警告......由于 Javascript 的类型很松散,你必须小心你的vArgumentTests
as aninteger
可以被验证为 afloat
等。
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}();
您现在可以在 ECMAScript 2018 中进行函数重载,无需 polyfill、检查 var 长度/类型等,只需使用扩展语法。
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"
ECMAScript 提案的 Rest/Spread Properties(第 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: 使用剩余(传播)参数
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 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"
});
函数重载是编程语言创建具有不同实现的多个同名函数的能力。当调用重载函数时,它将运行适合于调用上下文的该函数的特定实现。这个上下文通常是接收的参数数量,它允许一个函数调用根据上下文表现不同。
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');
在您不知道将获得多少参数的情况下,您可以使用rest 运算符,即三个点...
。它将剩余的参数转换成一个数组。不过要注意浏览器的兼容性。这是一个例子:
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 年):
2020
16160
12
65
2021
2023
更多信息:https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
我们提出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);
}
)
};
这是一个古老的问题,但我认为需要另一个条目(尽管我怀疑有人会阅读它)。立即调用函数表达式 (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
。这种模式可以重复任意次数,以赋予一个变量几个不同的功能定义。
我想分享一个类似重载方法的有用示例。
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 引用的面板
JavaScript 是无类型语言,我只认为在参数数量方面重载方法/函数才有意义。因此,我建议检查参数是否已定义:
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
对于您的用例,这就是我将如何处理它ES6
(因为它已经是 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;
}
您显然可以调整它以使用任何数量的参数,并相应地更改您的条件语句。
Typescript Handbook 提到了Overloads。虽然之前已经提到过检查类型并根据结果执行不同的逻辑,但这种定义多个命名函数以与类型系统一起工作的方法可能对读者来说很有趣。以下是 TypeScript 如何实现创建一个接受多种类型参数的函数,这些参数将指导函数逻辑根据传入的参数和类型执行不同的操作:
答案是为同一个函数提供多种函数类型作为重载列表。这个列表是编译器用来解析函数调用的。让我们创建一个重载列表来描述我们的 pickCard 接受什么以及它返回什么。
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 card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else 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);
通过此更改,重载现在为我们提供了对 pickCard 函数的类型检查调用。
为了让编译器选择正确的类型检查,它遵循与底层 JavaScript 类似的过程。它查看重载列表,并继续第一个重载,尝试使用提供的参数调用函数。如果找到匹配项,它将选择此重载作为正确的重载。出于这个原因,习惯上按照从最具体到最不具体的顺序对重载进行排序。
请注意,函数 pickCard(x):any piece 不是重载列表的一部分,因此它只有两个重载:一个接受对象,另一个接受数字。使用任何其他参数类型调用 pickCard 会导致错误。
第一个选项确实值得关注,因为这是我在相当复杂的代码设置中提出的。所以,我的回答是
- 首先使用不同的名称
有了一点但必不可少的提示,名称对于计算机来说应该看起来不同,但对你来说却不一样。命名重载函数,例如:func、func1、func2。
所以我真的很喜欢我在 javascript ninja 的秘密中发现的这种做事方式
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 语句,如果你开始编写复杂的代码,就会变得难以阅读(接受的答案将导致这种情况)。
就缺点而言,我猜代码最初有点晦涩......但我不确定其他人?
我喜欢@AntouanK 的方法。我经常发现自己提供了一个具有不同数量的参数和不同类型的函数。有时他们不遵守命令。我用来映射查看参数的类型:
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);
}
多年来,我一直在使用这个函数来美化我的重载:
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.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 );
} );
}
许多重载函数在优化性能时几乎是不可读的。你必须破译函数的头部。这可能比使用overload
我提供的功能更快;但是,从人类的角度来看,它在识别调用了哪个过载方面会比较慢。
我正在开发一个为 Javascript 提供类代码功能的库,目前它支持构造函数、继承、按参数数量和参数类型、mixin、静态属性和单例的方法重载。
请参阅使用该库的方法重载示例:
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
欢迎任何反馈、错误修复、文档和测试改进!