在Javascript中伪造函数重载的最好方法是什么?
我知道在Javascript中不可能像在其他语言中那样重载函数。 如果我需要一个函数有两个使用foo(x)和foo(x,y,z),这是最好的/首选的方式:
一开始就用不同的名字 使用可选参数,如y = y || 'default' 使用参数数量 检查参数类型 还是如何?
在Javascript中伪造函数重载的最好方法是什么?
我知道在Javascript中不可能像在其他语言中那样重载函数。 如果我需要一个函数有两个使用foo(x)和foo(x,y,z),这是最好的/首选的方式:
一开始就用不同的名字 使用可选参数,如y = y || 'default' 使用参数数量 检查参数类型 还是如何?
当前回答
我喜欢@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 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,等等]
#转发模式=> 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 ....
下面是一种使用参数类型允许真正的方法重载的方法,如下所示:
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Edit(2018):自2011年编写以来,直接方法调用的速度大大提高,而重载方法的速度却没有提高。
这不是我推荐的方法,但思考如何解决这类问题是一个值得思考的练习。
这里是不同方法的基准测试- https://jsperf.com/function-overloading。它显示函数重载(考虑到类型)可以在谷歌Chrome的V8 16.0(测试版)慢大约13倍。
除了传递一个对象(例如{x: 0, y: 0}),还可以在适当的时候采用C方法,相应地命名方法。例如,vector . addvector (vector), vector . addvector。AddIntegers(x, y, z,…)和Vector.AddArray(integerArray)。您可以查看C库,例如OpenGL,以获得命名灵感。
编辑:我已经添加了一个基准,用于传递一个对象,并使用arg和arg. hasownproperty ('param')中的'param'测试对象,函数重载比传递一个对象和检查属性快得多(至少在这个基准测试中)。
From a design perspective, function overloading is only valid or logical if the overloaded parameters correspond to the same action. So it stands to reason that there ought to be an underlying method that is only concerned with specific details, otherwise that may indicate inappropriate design choices. So one could also resolve the use of function overloading by converting data to a respective object. Of course one must consider the scope of the problem as there's no need in making elaborate designs if your intention is just to print a name, but for the design of frameworks and libraries such thought is justified.
我的例子来自一个矩形实现——因此提到了维和点。也许Rectangle可以向Dimension and Point原型添加一个GetRectangle()方法,然后对函数重载问题进行排序。那原语呢?好的,我们有参数length,这现在是一个有效的测试,因为对象有一个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);
对于函数重载,可以这样做。
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"
});
源
Typescript手册提到了重载。虽然前面已经提到过检查类型和根据结果执行不同的逻辑,但这种定义多个命名函数来使用类型系统的方法可能会让读者感兴趣。下面是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):任何块都不是重载列表的一部分,因此它只有两个重载:一个接受对象,另一个接受数字。用任何其他参数类型调用pickCard都会导致错误。