Sostituzione di una funzione JavaScript mentre si fa riferimento all'originale


160

Ho una funzione, a()che voglio sovrascrivere, ma anche l'originale deve a()essere eseguito in un ordine a seconda del contesto. Ad esempio, a volte quando sto generando una pagina, voglio sovrascrivere in questo modo:

function a() {
    new_code();
    original_a();
}

e a volte così:

function a() {
    original_a();
    other_new_code();
}

Come posso ottenerlo original_a()dall'over-riding a()? È anche possibile?

Per favore, non suggerire alternative all'over-riding in questo modo, ne conosco molte. Sto chiedendo questo in modo specifico.

Risposte:


179

Potresti fare qualcosa del genere:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Dichiarare original_aall'interno di una funzione anonima gli impedisce di ingombrare lo spazio dei nomi globale, ma è disponibile nelle funzioni interne.

Come Nerdmaster menzionato nei commenti, assicurati di includere ()alla fine. Si desidera chiamare la funzione esterna e memorizzare il risultato (una delle due funzioni interne) a, non memorizzare la funzione esterna stessa a.


25
Per tutti gli idioti là fuori come me - presta molta attenzione al "()" alla fine - senza questo, restituisce la funzione esterna, non le funzioni interne :)
Nerdmaster

@Nerdmaster Grazie per averlo segnalato. Ho aggiunto una nota per aiutare le persone a notare.
Matthew Crumley,

5
Wow, ho provato a farlo senza uno spazio dei nomi e ho avuto un overflow dello stack, lol.
gosukiwi,

penso che il risultato sarebbe lo stesso se la prima e l'ultima parentesi fossero rimosse dalla funzione. Come da qui (functi..e }). Il solo fatto }();produrrebbe lo stesso risultato, poiché il (function{})()primo set di parentesi viene utilizzato perché non puoi semplicemente dichiarare, anonimo, l'espressione della funzione che devi assegnare. Ma facendo () non stai definendo nulla semplicemente restituendo una funzione.
Muhammad Umer,

@MuhammadUmer Hai ragione sul fatto che le parentesi attorno all'espressione della funzione non sono necessarie. Li ho inclusi perché senza di essi, se si guardano solo le prime due righe, sembra (almeno per me) che tu stia assegnando direttamente la funzione esterna a, non chiamandola e memorizzando il valore restituito. Le parentesi mi fanno sapere che sta succedendo qualcos'altro.
Matthew Crumley,

88

Il modello proxy potrebbe aiutarti:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Quanto sopra avvolge il suo codice in una funzione per nascondere la variabile "proxy". Salva il metodo setArray di jQuery in una chiusura e lo sovrascrive. Il proxy quindi registra tutte le chiamate al metodo e delega la chiamata all'originale. L'uso di apply (questo, argomenti) garantisce che il chiamante non sarà in grado di notare la differenza tra il metodo originale e il metodo proxy.


10
infatti. l'uso di "applica" e "argomenti" rende questo molto più robusto rispetto alle altre risposte
Robert Levy,

Nota che questo modello non richiede jQuery: stanno solo usando una delle funzioni di jQuery come esempio.
Kev

28

Grazie ragazzi il modello proxy mi ha davvero aiutato ..... In realtà volevo chiamare una funzione globale ... In alcune pagine ho bisogno di fare alcuni controlli. Quindi ho fatto quanto segue.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Questo mi ha davvero aiutato


3
Quando si segue il modello proxy , in alcuni casi sarà importante implementare questo dettaglio che non è presente nel codice di questa risposta: Invece di org_foo(args), chiamare org_foo.call(this, args). Ciò mantiene thiscome sarebbe stato quando window.foo chiama normalmente (non in proxy). Vedi questa risposta
cellepo,

10

Puoi sovrascrivere una funzione usando un costrutto come:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Per esempio:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Modifica: risolto un errore di battitura.


+1 Questo è molto più leggibile rispetto agli altri esempi di modelli proxy!
Mu Mind,

1
... ma se non sbaglio, questo è limitato alle funzioni a argomento zero, o almeno ad un numero predeterminato di argomenti. C'è un modo per generalizzarlo a un numero arbitrario di argomenti senza perdere la leggibilità?
Mu Mind,

@MuMind: sì, ma usando correttamente il modello proxy - vedi la mia risposta .
Dan Dascalescu il

4

Passando argomenti arbitrari:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
Ma gli argomenti non si riferiscono a original_a?
Jonathan.

2

La risposta che @Matthew Crumley fornisce sta facendo uso delle espressioni di funzione immediatamente invocate, per chiudere la vecchia funzione 'a' nel contesto di esecuzione della funzione restituita. Penso che questa sia stata la risposta migliore, ma personalmente preferirei passare la funzione "a" come argomento all'IIFE. Penso che sia più comprensibile.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

Gli esempi precedenti non si applicano thiso passano argumentscorrettamente alla sostituzione della funzione. Underscore _.wrap () racchiude le funzioni esistenti, si applica thise passa argumentscorrettamente. Vedi: http://underscorejs.org/#wrap


0

Avevo scritto del codice da qualcun altro e volevo aggiungere una riga a una funzione che non riuscivo a trovare nel codice. Quindi, come soluzione alternativa, ho voluto sostituirlo.

Nessuna delle soluzioni ha funzionato per me però.

Ecco cosa ha funzionato nel mio caso:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

Ho creato un piccolo aiuto per uno scenario simile perché spesso avevo bisogno di sovrascrivere le funzioni di diverse librerie. Questo helper accetta uno "spazio dei nomi" (il contenitore delle funzioni), il nome della funzione e la funzione di sostituzione. Sostituirà la funzione originale nello spazio dei nomi indicato con quella nuova.

La nuova funzione accetta la funzione originale come primo argomento e gli argomenti delle funzioni originali come il resto. Conserverà il contesto ogni volta. Supporta anche funzioni void e non-void.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Utilizzo ad esempio con Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Tuttavia, non ho creato alcun test delle prestazioni. Può eventualmente aggiungere un overhead indesiderato che può o non può essere un grosso problema, a seconda degli scenari.


0

A mio avviso, le risposte migliori non sono leggibili / gestibili e le altre risposte non legano correttamente il contesto. Ecco una soluzione leggibile che utilizza la sintassi ES6 per risolvere entrambi questi problemi.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

Ma l'uso della sintassi ES6 lo rende piuttosto limitato nell'uso. Hai una versione che funziona in ES5?
Eitch

0

Quindi la mia risposta è risultata essere una soluzione che mi consente di utilizzare la variabile _questa che punta all'oggetto originale. Creo una nuova istanza di "Square", ma odiavo il modo in cui "Square" ha generato le sue dimensioni. Ho pensato che dovrebbe seguire le mie esigenze specifiche. Tuttavia, per fare ciò avevo bisogno che il quadrato avesse una funzione "GetSize" aggiornata con gli interni di quella funzione che chiamava altre funzioni già esistenti nel quadrato come this.height, this.GetVolume (). Ma per farlo avevo bisogno di farlo senza alcun trucco folle. Quindi ecco la mia soluzione.

Qualche altro inizializzatore di oggetti o funzione di aiuto.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Funzione nell'altro oggetto.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

Non sono sicuro che funzionerà in tutte le circostanze, ma nel nostro caso, stavamo cercando di ignorare la describefunzione in Jest in modo da poter analizzare il nome e saltare l'intero describeblocco se soddisfacesse alcuni criteri.

Ecco cosa ha funzionato per noi:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Due cose fondamentali qui:

  1. Non utilizziamo una funzione freccia () =>.

    Le funzioni freccia cambiano il riferimento in thise abbiamo bisogno che sia il file this.

  2. L'uso di this.describee this.describe.skipanziché solo describee describe.skip.

Ancora una volta, non sono sicuro che abbia valore per nessuno, ma inizialmente abbiamo cercato di cavarcela liberarci dell'eccellente risposta di Matthew Crumley, rendere il nostro metodo una funzione e accettare i parametri al fine di analizzarli nel condizionale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.