Come sovraccaricare le funzioni in javascript?

Approccio classico (non js) al sovraccarico:

function myFunc(){ //code } function myFunc(overloaded){ //other code } 

Javascript non permetterà di definire più di una funzione con lo stesso nome. Come tale, appaiono cose come questa:

 function myFunc(options){ if(options["overloaded"]){ //code } } 

C’è una soluzione migliore per sovraccaricare le funzioni in javascript oltre a passare un object con sovraccarichi in esso?

Passare sovraccarichi può far sì che una funzione diventi troppo prolissa perché ogni ansible sovraccarico richiederebbe un’istruzione condizionale. L’uso di funzioni per realizzare il //code all’interno di quelle istruzioni condizionali può causare situazioni difficili con gli ambiti.

Ci sono molti aspetti dell’argomento sovraccarico in Javascript:

  1. Argomenti variabili – È ansible passare diversi insiemi di argomenti (sia in tipo che in quantità) e la funzione si comporterà in modo che corrisponda agli argomenti passati.

  2. Argomenti predefiniti : è ansible definire un valore predefinito per un argomento se non viene passato.

  3. Argomenti con nome – L’ordine degli argomenti diventa irrilevante e basta nominare gli argomenti che si desidera passare alla funzione.

Di seguito è riportata una sezione su ciascuna di queste categorie di gestione degli argomenti.

Argomenti variabili

Poiché javascript non ha alcun tipo di controllo sugli argomenti o sulla qty richiesta di argomenti, puoi semplicemente avere un’implementazione di myFunc() che può adattarsi a quali argomenti sono stati passati controllando il tipo, la presenza o la quantità di argomenti.

jQuery fa tutto il tempo. Puoi rendere alcuni degli argomenti facoltativi o puoi ramificarti nella tua funzione a seconda di quali argomenti vengono passati ad esso.

Nell’implementazione di questi tipi di sovraccarichi, è ansible utilizzare diverse tecniche:

  1. È ansible verificare la presenza di qualsiasi argomento determinato controllando se il valore del nome dell’argomento dichiarato undefined è undefined .
  2. È ansible controllare la quantità totale o gli argomenti con arguments.length .
  3. Puoi controllare il tipo di ogni argomento dato.
  4. Per numeri variabili di argomenti, è ansible utilizzare gli arguments pseudo-array per accedere a qualsiasi argomento specificato con arguments[i] .

Ecco alcuni esempi:

Diamo un’occhiata al metodo obj.data() di jQuery. Supporta quattro diverse forms di utilizzo:

 obj.data("key"); obj.data("key", value); obj.data(); obj.data(object); 

Ognuno innesca un comportamento diverso e, senza utilizzare questa forma dynamic di sovraccarico, richiederebbe quattro funzioni separate.

Ecco come si può distinguere tra tutte queste opzioni in inglese e quindi le combinerò tutte in codice:

 // get the data element associated with a particular key value obj.data("key"); 

Se il primo argomento passato a .data() è una stringa e il secondo argomento undefined è undefined , il chiamante deve utilizzare questo modulo.


 // set the value associated with a particular key obj.data("key", value); 

Se il secondo argomento non è indefinito, imposta il valore di una particolare chiave.


 // get all keys/values obj.data(); 

Se non vengono passati argomenti, quindi restituire tutte le chiavi / valori in un object restituito.


 // set all keys/values from the passed in object obj.data(object); 

Se il tipo del primo argomento è un object semplice, imposta tutte le chiavi / i valori da quell’object.


Ecco come puoi combinare tutti quelli in un set di logica 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 } }, 

La chiave di questa tecnica è di assicurarsi che tutte le forms di argomenti che si desidera accettare siano identificabili in modo univoco e non ci sia mai alcuna confusione su quale modulo sta utilizzando il chiamante. Questo in genere richiede di ordinare gli argomenti in modo appropriato e assicurandosi che ci sia abbastanza unicità nel tipo e nella posizione degli argomenti che puoi sempre dire quale forma viene usata.

Ad esempio, se hai una funzione che accetta tre argomenti stringa:

 obj.query("firstArg", "secondArg", "thirdArg"); 

Puoi facilmente rendere facoltativo il terzo argomento e puoi facilmente rilevare tale condizione, ma non puoi rendere facoltativo solo il secondo argomento perché non puoi dire quale di questi significhi passare il chiamante perché non c’è modo di identificare se il secondo argomento è pensato per essere il secondo argomento o il secondo argomento è stato omesso, quindi quello che è nel punto del secondo argomento è in realtà il terzo argomento:

 obj.query("firstArg", "secondArg"); obj.query("firstArg", "thirdArg"); 

Poiché tutti e tre gli argomenti sono dello stesso tipo, non è ansible distinguere tra diversi argomenti in modo da non sapere cosa intendesse il chiamante. Con questo stile di chiamata, solo il terzo argomento può essere facoltativo. Se si desidera omettere il secondo argomento, dovrebbe essere passato come null (o qualche altro valore rilevabile) e il vostro codice rileverà che:

 obj.query("firstArg", null, "thirdArg"); 

Ecco un esempio jQuery di argomenti opzionali. entrambi gli argomenti sono facoltativi e assumono valori predefiniti se non vengono passati:

 clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function () { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); }, 

Ecco un esempio di jQuery in cui l’argomento può essere mancante o uno dei tre diversi tipi che ti dà quattro diversi overload:

 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>"); 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; }, 

Argomenti nominati

Altri linguaggi (come Python) consentono di passare argomenti con nome come mezzo per passare solo alcuni argomenti e rendere gli argomenti indipendenti dall'ordine in cui sono passati. Javascript non supporta direttamente la funzione degli argomenti con nome. Un modello di progettazione che viene comunemente utilizzato al suo posto è quello di passare una mappa di proprietà / valori. Questo può essere fatto passando un object con proprietà e valori oppure in ES6 e versioni successive, potresti effettivamente passare un object Map stesso.

Ecco un semplice esempio ES5:

$.ajax() jQuery accetta una forma di utilizzo in cui si passa semplicemente un singolo parametro che è un normale object Javascript con proprietà e valori. Quali proprietà si passano determina quali argomenti / opzioni vengono passati alla chiamata Ajax. Alcuni possono essere richiesti, molti sono opzionali. Poiché sono proprietà su un object, non esiste un ordine specifico. Infatti, ci sono più di 30 proprietà differenti che possono essere trasmesse su quell'object, solo uno (l'url) è richiesto.

Ecco un esempio:

 $.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) { // process result here }); 

All'interno dell'implementazione $.ajax() , può quindi semplicemente interrogare quali proprietà sono state passate sull'object in entrata e usarle come argomenti con nome. Questo può essere fatto con for (prop in obj) o ottenendo tutte le proprietà in un array con Object.keys(obj) e quindi iterando quella matrice.

Questa tecnica è usata molto comunemente in Javascript quando ci sono un gran numero di argomenti e / o molti argomenti sono opzionali. Nota: questo pone un onere sulla funzione di implementazione per assicurarsi che sia presente un minimo insieme valido di argomenti e per fornire al chiamante un feedback di debug che cosa manca se vengono passati argomenti insufficienti (probabilmente lanciando un'eccezione con un messaggio di errore utile) .

In un ambiente ES6, è ansible utilizzare la destrutturazione per creare proprietà / valori predefiniti per l'object passato sopra. Questo è discusso più dettagliatamente in questo articolo di riferimento .

Ecco un esempio di questo articolo:

 function selectEntries({ start=0, end=-1, step=1 } = {}) { ··· }; 

Ciò crea proprietà e valori predefiniti per le proprietà start , end e step su un object passato alla funzione selectEntries() .

Valori predefiniti per argomenti di funzione

In ES6, Javascript aggiunge il supporto lingua incorporato per i valori predefiniti per gli argomenti.

Per esempio:

 function multiply(a, b = 1) { return a*b; } multiply(5); // 5 

Ulteriore descrizione dei modi in cui questo può essere usato qui su MDN .

Il sovraccarico di una funzione in JavaScript può essere fatto in molti modi. Tutti implicano una singola funzione master che esegue tutti i processi o delegati a funzioni / processi secondari.

Una delle più comuni tecniche semplici prevede un semplice passaggio:

 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; } } 

Una tecnica più elegante sarebbe usare un array (o un object se non si fanno sovraccarichi per ogni argomento):

 fooArr = [ function () { }, function (a) { }, function (a,b) { } ]; function foo(a, b) { return fooArr[arguments.length](a, b); } 

Quell’esempio precedente non è molto elegante, chiunque potrebbe modificare fooArr , e fallirebbe se qualcuno passasse più di 2 argomenti a foo , quindi una forma migliore sarebbe usare un modello di modulo e alcuni controlli:

 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; }()); 

Ovviamente i sovraccarichi potrebbero voler utilizzare un numero dinamico di parametri, quindi è ansible utilizzare un object per la raccolta 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; }()); 

La mia preferenza personale tende ad essere l’ switch , sebbene rafforzi la funzione principale. Un esempio comune di dove utilizzerei questa tecnica sarebbe un metodo accessor / mutator:

 function Foo() {} //constructor Foo.prototype = { bar: function (val) { switch (arguments.length) { case 0: return this._bar; case 1: this._bar = val; return this; } } } 

Non si può fare un sovraccarico di metodo in senso stretto. Non come il modo in cui è supportato in java o c# .

Il problema è che JavaScript NON supporta in modo nativo l’overloading dei metodi. Quindi, se vede / analizza due o più funzioni con lo stesso nome, prenderà in considerazione solo l’ultima funzione definita e sovrascriverà quelle precedenti.

Uno dei modi in cui penso sia adatto per la maggior parte del caso è il seguente:

Diciamo che hai un metodo

 function foo(x) { } 

Invece di un metodo di overload che non è ansible in javascript , puoi definire un nuovo metodo

 fooNew(x,y,z) { } 

e quindi modificare la 1a funzione come segue:

 function foo(x) { if(arguments.length==2) { return fooNew(arguments[0], arguments[1]); } } 

Se si dispone di molti di questi metodi sovraccarichi, si consideri l’utilizzo di switch piuttosto che istruzioni if-else .

( maggiori dettagli )

Sto usando un approccio di overload leggermente diverso basato sul numero di argomenti. Comunque credo che anche l’approccio di John Fawcett sia buono. Ecco l’esempio, codice basato sulle spiegazioni di John Resig (jQuery’s Author).

 // 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); } }; } 

usabilità:

 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 :) 

Ho provato a sviluppare una soluzione elegante a questo problema descritto qui . E puoi trovare la demo qui . L’utilizzo è simile a questo:

 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); 

I metodi usati per ottenere questo:

 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" 

In javascript puoi implementare la funzione una volta sola e richiamare la funzione senza i parametri myFunc() Quindi verifica se le opzioni sono ‘indefinite’

 function myFunc(options){ if(typeof options != 'undefined'){ //code } } 

Controllalo:

http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions

Fondamentalmente nella tua class, numeri le tue funzioni che vuoi sovraccaricare e poi con una chiamata di funzione aggiungi sovraccarico di funzione, veloce e facile.

Poiché JavaScript non ha opzioni di sovraccarico di funzione , è ansible utilizzare invece l’object. Se ci sono uno o due argomenti richiesti, è meglio tenerli separati dall’object opzioni. Ecco un esempio su come utilizzare le opzioni object e valori popolati sul valore predefinito nel caso in cui il valore non fosse passato nell’object opzioni.

 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 + "}"; 

}

ecco un esempio su come usare l’object options

Per questo è necessario creare una funzione che aggiunge la funzione a un object, quindi verrà eseguita in base alla quantità di argomenti inviati alla funzione:

  

Nessun problema con il sovraccarico in JS, il pb come mantenere un codice pulito in caso di sovraccarico?

Puoi usare un forward per avere un codice pulito, basato su due cose:

  1. Numero di argomenti (quando si chiama la funzione).
  2. Tipo di argomenti (quando si chiama la funzione)

      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 .... 

Mi piace aggiungere funzioni secondarie all’interno di una funzione genitore per ottenere la possibilità di distinguere tra gruppi di argomenti per la stessa funzionalità.

 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; }; 

Quello che stai cercando di ottenere è meglio farlo usando la variabile degli argomenti locali della funzione.

 function foo() { if (arguments.length === 0) { //do something } if (arguments.length === 1) { //do something else } } foo(); //do something foo('one'); //do something else 

Puoi trovare una spiegazione migliore di come funziona qui .