Quel est le meilleur moyen de simuler une surcharge de fonction en Javascript?
Je sais qu'il n'est pas possible de surcharger des fonctions en Javascript comme dans d'autres langages. Si j'avais besoin d'une fonction avec deux utilisations, foo(x)
et foo(x,y,z)
, qui est la meilleure façon/la méthode préférée:
y = y || 'default'
La meilleure façon de surcharger les fonctions avec des paramètres est de ne pas vérifier la longueur de l'argument ni les types; vérifier les types ne fera que ralentir votre code et vous permettre de vous amuser avec des tableaux, des valeurs nulles, des objets, etc.
Ce que la plupart des développeurs font, c'est d'utiliser un objet comme dernier argument de leurs méthodes. Cet objet peut contenir n'importe quoi.
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"});
Ensuite, vous pouvez le gérer comme vous le souhaitez dans votre méthode. [Switch, si-sinon, etc.]
Je fais souvent ça:
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
Équivalent 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
Cet exemple particulier est en réalité plus élégant en javascript qu'en C #. Les paramètres non spécifiés sont 'undefined' en javascript, ce qui donne False dans une instruction if. Cependant, la définition de la fonction ne transmet pas l’information que p2 et p3 sont facultatifs. Si vous avez besoin de beaucoup de surcharge, jQuery a décidé d'utiliser un objet comme paramètre, par exemple, jQuery.ajax (options). Je suis d’accord avec eux pour dire que c’est l’approche la plus puissante et la plus clairement documentable de la surcharge, mais j’ai rarement besoin de plus d’un ou deux paramètres optionnels rapides.
EDIT: modification du test IF selon la suggestion de Ian
Il n’ya pas de surcharge de fonction réelle dans JavaScript car cela permet de transmettre un nombre quelconque de paramètres de tout type. Vous devez vérifier dans la fonction combien de arguments ont été passés et quel type ils sont.
La bonne réponse est: IS PAS DE SURCHARGE DANS JAVASCRIPT.
Vérifier/basculer à l'intérieur de la fonction n'est pas une SURCHARGE.
Le concept de surcharge: Dans certains langages de programmation, la surcharge de fonctions ou la surcharge de méthodes désigne la possibilité de créer plusieurs méthodes du même nom avec différentes implémentations. Les appels à une fonction surchargée exécutent une implémentation spécifique de cette fonction appropriée au contexte de l'appel, permettant à un appel de fonction d'exécuter différentes tâches en fonction du contexte.
Par exemple, doTask () et doTask (objet O) sont des méthodes surchargées. Pour appeler ce dernier, un objet doit être passé en tant que paramètre, alors que le premier ne nécessite pas de paramètre et est appelé avec un champ de paramètre vide. Une erreur courante serait d’attribuer une valeur par défaut à l’objet dans la deuxième méthode, ce qui entraînerait une erreur d’appel ambiguë, car le compilateur ne saurait pas laquelle des deux méthodes utiliser.
https://en.wikipedia.org/wiki/overloading
Toutes les implémentations suggérées sont excellentes, mais à vrai dire, il n’existe pas d’implémentation native pour JavaScript.
Il y a deux façons de mieux aborder cette question:
Passez un dictionnaire (tableau associatif) si vous voulez laisser beaucoup de flexibilité
Prenez un objet comme argument et utilisez un héritage basé sur un prototype pour ajouter de la flexibilité.
Voici une approche permettant une surcharge de méthode réelle à l'aide de types de paramètres, illustrés ci-dessous:
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Edit (2018): Depuis sa rédaction en 2011, la vitesse des appels de méthodes directes a considérablement augmenté, contrairement à celle des méthodes surchargées.
Ce n’est pas une approche que je recommanderais, mais c’est un exercice de réflexion utile qui permet de réfléchir à la manière de résoudre ce type de problèmes.
Voici un repère des différentes approches - https://jsperf.com/function-overloading . Cela montre que la surcharge de fonctions (en tenant compte des types) peut être environ 13 fois plus lente dans le V8 de Google Chrome à partir de 16.0 (beta).
En plus de transmettre un objet (c'est-à-dire {x: 0, y: 0}
), on peut également adopter l'approche C le cas échéant, en nommant les méthodes en conséquence. Par exemple, Vector.AddVector (vector), Vector.AddIntegers (x, y, z, ...) et Vector.AddArray (integerArray). Vous pouvez consulter les bibliothèques C, telles que OpenGL pour nommer l'inspiration.
Edit: j'ai ajouté un point de repère pour passer un objet et tester l'objet à l'aide de 'param' in arg
et de arg.hasOwnProperty('param')
, et la surcharge de fonctions est beaucoup plus rapide que de passer un objet et de rechercher des propriétés (au moins dans ce critère).
Du point de vue de la conception, la surcharge de fonctions n’est valide ou logique que si les paramètres surchargés correspondent à la même action. Il va donc de soi qu'il devrait exister une méthode sous-jacente qui ne concerne que des détails spécifiques, sinon cela peut indiquer des choix de conception inappropriés. Donc, on pourrait également résoudre l'utilisation de la surcharge de fonction en convertissant les données en un objet respectif. Bien entendu, il faut tenir compte de l’ampleur du problème, car il n’est pas nécessaire de créer des motifs élaborés si vous souhaitez uniquement imprimer un nom, mais cette conception est justifiée pour la conception de cadres et de bibliothèques.
Mon exemple vient d'une implémentation Rectangle - d'où la mention de Dimension et Point. Peut-être que Rectangle pourrait ajouter une méthode GetRectangle()
au prototype Dimension
et Point
, puis le problème de surcharge de fonctions est trié. Et qu'en est-il des primitifs? Nous avons bien la longueur des arguments, qui est maintenant un test valide puisque les objets ont une méthode 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);
Le meilleur moyen dépend vraiment de la fonction et des arguments. Chacune de vos options est une bonne idée dans différentes situations. J'essaie généralement de les essayer dans l'ordre suivant jusqu'à ce que l'un d'entre eux fonctionne:
Utilisation d'arguments optionnels comme y = y || 'défaut'. Ceci est pratique si vous pouvez le faire, mais cela peut ne pas toujours fonctionner de manière pratique, par exemple. quand 0/null/undefined serait un argument valide.
Utilisation du nombre d'arguments. Similaire à la dernière option mais peut fonctionner lorsque # 1 ne fonctionne pas.
Vérification des types d'arguments. Ceci peut fonctionner dans certains cas où le nombre d'arguments est le même. Si vous ne pouvez pas déterminer les types de manière fiable, vous devrez peut-être utiliser des noms différents.
Utiliser des noms différents en premier lieu. Vous devrez peut-être faire cela si les autres options ne fonctionnent pas, si elles ne sont pas pratiques ou si elles sont compatibles avec d'autres fonctions connexes.
Si j'avais besoin d'une fonction avec deux utilisations foo (x) et foo (x, y, z), quelle est la meilleure méthode/la méthode préférée?
Le problème est que JavaScript ne supporte PAS nativement la surcharge de méthodes. Ainsi, s’il voit/analyse deux ou plusieurs fonctions portant le même nom, il considérera simplement la dernière fonction définie et écrasera les précédentes.
Je pense que l’une des solutions possibles dans la plupart des cas est la suivante:
Disons que vous avez la méthode
function foo(x)
{
}
Au lieu de surcharger la méthode qui n’est pas possible en javascript, vous pouvez définir une nouvelle méthode
fooNew(x,y,z)
{
}
puis modifiez la 1ère fonction comme suit -
function foo(arguments)
{
if(arguments.length==2)
{
return fooNew(arguments[0], arguments[1]);
}
}
Si vous avez beaucoup de méthodes surchargées, envisagez d'utiliser switch
plutôt que des instructions if-else
.
( plus de détails )
PS: Le lien ci-dessus mène à mon blog personnel qui contient des détails supplémentaires.
Je ne suis pas sûr de la meilleure pratique, mais voici comment je le fais:
/*
* 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"
Je viens d’essayer cela, peut-être que cela répond à vos besoins ... Selon le nombre d’arguments, vous pouvez accéder à une fonction différente. Vous l'initialisez la première fois que vous l'appelez ..__ Et la carte des fonctions est masquée dans la fermeture.
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;
};
Essaye-le.
TEST.multiFn("0")
.multiFn()
.multiFn("0","1","2");
Puisque JavaScript n’a pas d’option de surcharge de fonction, l’objet peut être utilisé à la place. S'il y a un ou deux arguments obligatoires, il est préférable de les séparer de l'objet options. Voici un exemple d'utilisation d'objet options et de valeurs renseignées avec la valeur par défaut au cas où cette valeur ne serait pas passée dans l'objet options.
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 + "}";
}
here est un exemple d'utilisation d'objet options
Il n’existe aucun moyen de surcharger la fonction en javascript . Je recommande donc de suivre la méthode suivante avec la méthode typeof()
au lieu de Plusieurs fonctions pour simuler une surcharge.
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!!");
}
}
Bonne chance!
Une autre façon de procéder consiste à utiliser la variable spéciale: arguments , il s’agit d’une implémentation:
function sum() {
var x = 0;
for (var i = 0; i < arguments.length; ++i) {
x += arguments[i];
}
return x;
}
afin que vous puissiez modifier ce code pour:
function sum(){
var s = 0;
if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
return s;
}
regarde ça. C'est très cool . http://ejohn.org/blog/javascript-method-overloading/ Astuce Javascript pour vous permettre d'effectuer des appels comme celui-ci:
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
Comme ce message contient déjà beaucoup de solutions différentes, je pensais en poster une autre.
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");
}
cela peut être utilisé comme indiqué ci-dessous:
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));
Cette solution n'est pas parfaite, mais je veux seulement montrer comment cela pourrait être fait.
Vous pouvez désormais surcharger des fonctions dans ECMAScript 2018 sans polyfill, sans vérifier var longueur/type, etc., utilisez simplement la syntaxe spread .
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"
La proposition Propriétés de repos/propagation pour ECMAScript (étape 4) ajoute des propriétés de propagation aux littéraux d'objet. Il copie ses propres propriétés énumérables d'un objet fourni dans un nouvel objet. Plus sur mdn
Remarque: la syntaxe de propagation dans les littéraux d'objet ne fonctionne pas dans Edge et IE et il s'agit d'une fonctionnalité expérimentale. voir la compatibilité du navigateur
Vous pouvez utiliser le 'addMethod' de John Resig. Avec cette méthode, vous pouvez "surcharger" les méthodes en fonction du nombre d'arguments.
// 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 );
};
}
J'ai également créé une alternative à cette méthode qui utilise la mise en cache pour conserver les variations de la fonction. Les différences sont décrites ici
// 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);
};
}
Cela provient d'un corps de code plus large qui inclut les fonctions de vérification de type isFn
, isArr
, etc. La version de VanillaJS ci-dessous a été retravaillée pour supprimer toutes les dépendances externes. Toutefois, vous devrez définir vos propres fonctions de vérification de type à utiliser dans les appels .add()
.
Remarque: Il s'agit d'une fonction à exécution automatique (nous pouvons donc avoir une portée de fermeture/fermeture), d'où l'affectation à window.overload
plutôt qu'à 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;
}();
L'appelant définit ses fonctions surchargées en affectant une variable au retour de overload()
. Grâce au chaînage, les surcharges supplémentaires peuvent être définies en série:
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")
;
Le seul argument optionnel de overload()
définit la fonction "par défaut" à appeler si la signature ne peut pas être identifiée. Les arguments pour .add()
sont:
fn
: function
définissant la surcharge;a_vArgumentTests
: Array
of function
s définissant les tests à exécuter sur la arguments
. Chaque function
accepte un seul argument et renvoie true
thy en fonction de la validité de l'argument;sAlias
(Facultatif): string
définissant l'alias permettant d'accéder directement à la fonction de surcharge (fn
), par exemple. myOverloadedFn.noArgs()
appellera cette fonction directement, en évitant les tests de polymorphisme dynamique des arguments.Cette implémentation autorise en réalité davantage que les surcharges de fonctions traditionnelles, car le deuxième argument a_vArgumentTests
de .add()
définit en pratique les types personnalisés. Vous pouvez donc créer des arguments non seulement en fonction du type, mais également en fonction de plages, de valeurs ou de collections de valeurs!
Si vous examinez les 145 lignes de code pour overload()
, vous constaterez que chaque signature est classée selon le nombre de arguments
qui lui ont été transmis. Ceci est fait pour limiter le nombre de tests que nous exécutons. Je surveille également le nombre d'appels. Avec un peu de code supplémentaire, les tableaux de fonctions surchargées pourraient être triés de nouveau afin que les fonctions les plus communément appelées soient testées en premier, ce qui ajoute encore une fois une mesure d'amélioration des performances.
Maintenant, il y a quelques réserves ... Comme Javascript est typé de manière approximative, vous devrez faire attention à votre vArgumentTests
car une integer
pourrait être validée comme une float
, etc.
Version JSCompress.com (1114 octets, 744 octets g-zippés):
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}();
Transférer vers une autre fonction dont le nom est construit à partir des 3ème et 4ème points:
- Utilisation du nombre d'arguments
- Vérification des types d'arguments
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 ....
La première option mérite vraiment l’attention, car c’est ce que j’ai découvert dans une configuration de code assez complexe. Donc, ma réponse est
- Utiliser des noms différents en premier lieu
Avec un indice, mais essentiel, les noms devraient avoir une apparence différente pour l'ordinateur, mais pas pour vous. Nommez les fonctions surchargées telles que: func, func1, func2.
Pour votre cas d'utilisation, voici comment je l'aborderais avec ES6
(puisque c'est déjà la fin de 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;
}
Vous pouvez évidemment adapter cela pour fonctionner avec n'importe quel nombre de paramètres et simplement modifier vos instructions conditionnelles en conséquence.
JavaScript est un langage non typé et je pense seulement que cela a du sens de surcharger une méthode/fonction par rapport au nombre de paramètres Par conséquent, je recommanderais de vérifier si le paramètre a été défini:
myFunction = function(a, b, c) {
if (b === undefined && c === undefined ){
// do x...
}
else {
// do y...
}
};
À compter de juillet 2017, voici la technique courante. Notez que nous pouvons également effectuer une vérification de type dans la fonction.
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
C’est une vieille question, mais je pense qu’elle nécessite une autre entrée (même si je doute que quelqu'un la lise). L'utilisation des expressions de fonction immédiatement appelées (IIFE) peut être utilisée conjointement avec des fonctions de fermeture et des fonctions en ligne pour permettre une surcharge des fonctions. Prenons l'exemple suivant (artificiel):
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
En bref, l'utilisation de IIFE crée une portée locale, nous permettant de définir la variable privée old
afin de stocker une référence à la définition initiale de la fonction foo
. Cette fonction retourne ensuite une fonction inline newFoo
qui enregistre le contenu des deux arguments si elle est passée avec exactement deux arguments a
et b
ou appelle la fonction old
si arguments.length !== 2
. Ce modèle peut être répété autant de fois que nécessaire pour doter une variable de plusieurs définitions fonctionnelles différentes.
Quelque chose comme ceci peut être fait pour surcharger une fonction.
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"
});
Je voudrais partager un exemple utile d’approche surchargée.
function Clear(control)
{
var o = typeof control !== "undefined" ? control : document.body;
var children = o.childNodes;
while (o.childNodes.length > 0)
o.removeChild(o.firstChild);
}
Utilisation: Clair(); // Efface tout le document
Clair (myDiv); // Efface le panneau référencé par myDiv
J'utilise cette fonction pour embellir mes surcharges depuis des années:
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);
}
}
Démostré:
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);
Jetez un coup d'œil à la méthode off
de jQuery:
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 );
} );
}
De nombreuses fonctions surchargées, lorsqu'elles sont optimisées pour la performance, sont pratiquement illisibles. Vous devez déchiffrer la tête de la fonction. C'est peut-être plus rapide que d'utiliser une fonction overload
comme celle que je fournis; Cependant, il est plus lent, du point de vue humain, de déterminer quelle surcharge a été appelée.
il n'y a pas de surcharge réelle dans JS, mais nous pouvons quand même simuler la surcharge de méthode de plusieurs manières:
méthode n ° 1: utiliser un objet
function test(x,options){
if("a" in options)doSomething();
else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});
méthode n ° 2: utilise les paramètres rest (spread)
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");
}
méthode n ° 3: utilisation non définie
function test(x, y, z){
if(typeof(z)=="undefined")doSomething();
}
méthode n ° 4: vérification du type
function test(x){
if(typeof(x)=="string")console.log("a string passed")
else ...
}
Nous avons fait over.js résoudre ce problème est une manière très élégante. Tu peux faire:
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);
}
)
};
J'aime l'approche de @ AntouanK. Je me trouve souvent en train de proposer une fonction avec des nombres ou des paramètres différents. Parfois, ils ne suivent pas un ordre. J'utilise pour cartographier les types de paramètres:
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);
}
J'ai donc beaucoup aimé cette façon de faire que j'ai trouvée dans les secrets du ninja 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);
}
}
}
vous utilisez ensuite addMethod pour ajouter des fonctions surchargées à n’importe quel objet. La principale confusion dans ce code a été pour moi l'utilisation de fn.length == arguments.length - cela fonctionne car fn.length est le nombre de paramètres attendus, alors que arguments.length est le nombre de paramètres qui sont réellement appelés une fonction. La raison pour laquelle la fonction anonyme n'a pas d'argument est que vous pouvez passer n'importe quel nombre d'arguments en javascript et que le langage pardonne.
J'ai aimé cela parce que vous pouvez l'utiliser partout - créez simplement cette fonction et utilisez simplement la méthode dans la base de code de votre choix.
Cela évite également d'avoir une instruction if/switch ridiculement grande, qui devient difficile à lire si vous commencez à écrire du code complexe (la réponse acceptée en résultera).
En ce qui concerne les inconvénients, je suppose que le code est initialement un peu obscur ... mais je ne suis pas sûr des autres?
La surcharge de fonctions est la capacité d'un langage de programmation à créer plusieurs fonctions du même nom avec des implémentations différentes. Lorsqu'une fonction surchargée est appelée, elle exécutera fonction une implémentation spécifique de cette fonction appropriée au contexte de l'appel. Ce contexte correspond généralement à la quantité d'arguments reçus et permet à un appel de fonction de se comporter différemment en fonction du contexte.
Javascript n'a pas une surcharge de fonctions intégrée. Cependant, ce comportement peut être imité de plusieurs manières. Voici un simple pratique:
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');
Dans les scénarios où vous ne savez pas combien d'arguments vous obtiendrez, vous pouvez utiliser l'opérateur rest , qui est constitué de trois points ...
. Cela convertira le reste des arguments en un tableau. Attention cependant à la compatibilité du navigateur. Voici un exemple:
function foo (a, ...b) {
console.log(b);
}
foo(1,2,3,4);
foo(1,2);