Überschreiben einer JavaScript-Funktion beim Verweisen auf das Original

160

Ich habe eine Funktion, a()die ich überschreiben möchte, aber auch das Original a()in einer Reihenfolge ausführen lassen soll, die vom Kontext abhängt. Zum Beispiel möchte ich manchmal beim Generieren einer Seite Folgendes überschreiben:

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

und manchmal so:

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

Wie bekomme ich das original_a()aus dem Overriding heraus a()? Ist es überhaupt möglich?

Bitte schlagen Sie auf diese Weise keine Alternativen zum Überfahren vor, ich kenne viele. Ich frage speziell nach diesem Weg.

Kev
quelle

Antworten:

179

Sie könnten so etwas tun:

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

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

Das Deklarieren original_ainnerhalb einer anonymen Funktion verhindert, dass der globale Namespace überladen wird, ist jedoch in den inneren Funktionen verfügbar.

Wie Nerdmaster in den Kommentaren erwähnt, stellen Sie sicher, dass Sie das ()am Ende einfügen . Sie möchten die äußere Funktion aufrufen und das Ergebnis (eine der beiden inneren Funktionen) in aspeichern, nicht die äußere Funktion selbst in a.

Matthew Crumley
quelle
25
Für alle Idioten da draußen wie mich - achten Sie genau auf das "()" am Ende - ohne das gibt es die äußere Funktion zurück, nicht die inneren Funktionen :)
Nerdmaster
@Nerdmaster Danke, dass du darauf hingewiesen hast. Ich habe eine Notiz hinzugefügt, um den Leuten hoffentlich zu helfen, es zu bemerken.
Matthew Crumley
5
Wow, ich habe versucht, dies ohne einen Namespace zu tun, und ich habe einen Stapelüberlauf bekommen, lol.
Gosukiwi
Ich denke, das Ergebnis wäre das gleiche, wenn die ersten und letzten Klammern aus der Funktion entfernt würden. Wie von hier (functi..und }). Nur dies }();würde das gleiche Ergebnis liefern, da der (function{})()erste Satz von Klammern verwendet wird, da Sie nicht einfach einen anonymen Funktionsausdruck deklarieren können, den Sie ihm zuweisen müssen. Mit doing () definieren Sie jedoch nichts, was nur eine Funktion zurückgibt.
Muhammad Umer
@MuhammadUmer Sie haben Recht, dass die Klammern um den Funktionsausdruck nicht erforderlich sind. Ich habe sie eingeschlossen, weil es ohne sie, wenn Sie nur die ersten paar Zeilen betrachten, (zumindest für mich) so aussieht, als würden Sie die äußere Funktion direkt zuweisen a, sie nicht aufrufen und den Rückgabewert speichern. Die Klammern lassen mich wissen, dass noch etwas los ist.
Matthew Crumley
88

Das Proxy-Muster könnte Ihnen helfen:

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

Das Obige umschließt seinen Code mit einer Funktion, um die "Proxy" -Variable auszublenden. Es speichert die setArray-Methode von jQuery in einem Closure und überschreibt sie. Der Proxy protokolliert dann alle Aufrufe an die Methode und delegiert den Aufruf an das Original. Die Verwendung von apply (dies, Argumente) garantiert, dass der Aufrufer den Unterschied zwischen der ursprünglichen und der Proxy-Methode nicht bemerken kann.

PhilHoy
quelle
10
tatsächlich. Die Verwendung von 'anwenden' und 'Argumenten' macht dies weitaus robuster als die anderen Antworten
Robert Levy
Beachten Sie, dass für dieses Muster keine jQuery erforderlich ist. Sie verwenden lediglich eine der Funktionen von jQuery als Beispiel.
Kev
28

Vielen Dank, das Proxy-Muster hat wirklich geholfen. Eigentlich wollte ich eine globale Funktion foo aufrufen. Auf bestimmten Seiten muss ich einige Überprüfungen durchführen. Also habe ich folgendes gemacht.

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

Danke, das hat mir wirklich geholfen

Naresh S.
quelle
3
Wenn Sie dem Proxy-Muster folgen , ist es in einigen Fällen wichtig, dieses Detail zu implementieren, das im Code dieser Antwort nicht enthalten ist: org_foo(args)Rufen Sie stattdessen auf org_foo.call(this, args). Das thisbleibt so , wie es gewesen wäre, wenn window.foo normal aufgerufen hätte (nicht als Proxy). Siehe diese Antwort
cellepo
10

Sie können eine Funktion mit einem Konstrukt wie dem folgenden überschreiben:

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

Beispielsweise:

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

Bearbeiten: Tippfehler behoben.

Hai Phan
quelle
+1 Dies ist so viel besser lesbar als die anderen Beispiele für Proxy-Muster!
Mu Mind
1
... aber wenn ich mich nicht irre, ist dies auf Nullargumentfunktionen oder zumindest eine vorgegebene Anzahl von Argumenten beschränkt. Gibt es eine Möglichkeit, es auf eine beliebige Anzahl von Argumenten zu verallgemeinern, ohne die Lesbarkeit zu verlieren?
Mu Mind
@ MuMind: Ja, aber das Proxy-Muster richtig verwenden - siehe meine Antwort .
Dan Dascalescu
4

Übergabe beliebiger Argumente:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});
Edric Garran
quelle
1
Beziehen sich Argumente nicht darauf original_a?
Jonathan.
2

Die Antwort von @Matthew Crumley besteht darin, die sofort aufgerufenen Funktionsausdrücke zu verwenden, um die ältere 'a'-Funktion im Ausführungskontext der zurückgegebenen Funktion zu schließen. Ich denke, dies war die beste Antwort, aber ich persönlich würde es vorziehen, die Funktion 'a' als Argument an IIFE zu übergeben. Ich finde es verständlicher.

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

Die obigen Beispiele gelten nicht richtig thisoder werden nicht argumentskorrekt an die Funktionsüberschreibung übergeben. Unterstrich _.wrap () umschließt vorhandene Funktionen, wendet sie an thisund wird argumentskorrekt übergeben. Siehe: http://underscorejs.org/#wrap

nevf
quelle
0

Ich hatte einen Code von jemand anderem geschrieben und wollte einer Funktion eine Zeile hinzufügen, die ich im Code nicht finden konnte. Als Workaround wollte ich es überschreiben.

Keine der Lösungen hat bei mir funktioniert.

Folgendes hat in meinem Fall funktioniert:

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

Ich habe einen kleinen Helfer für ein ähnliches Szenario erstellt, da ich häufig Funktionen aus mehreren Bibliotheken überschreiben musste. Dieser Helfer akzeptiert einen "Namespace" (den Funktionscontainer), den Funktionsnamen und die überschreibende Funktion. Die ursprüngliche Funktion im angegebenen Namespace wird durch die neue ersetzt.

Die neue Funktion akzeptiert die ursprüngliche Funktion als erstes Argument und die ursprünglichen Funktionsargumente als Rest. Der Kontext wird jedes Mal beibehalten. Es unterstützt auch leere und nicht leere Funktionen.

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

Verwendung zum Beispiel mit Bootstrap:

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

Ich habe jedoch keine Leistungstests erstellt. Es kann möglicherweise zu unerwünschtem Overhead kommen, der je nach Szenario eine große Sache sein kann oder nicht.

Zoltán Tamási
quelle
0

Meiner Meinung nach sind die Top-Antworten nicht lesbar / wartbar, und die anderen Antworten binden den Kontext nicht richtig. Hier ist eine lesbare Lösung mit ES6-Syntax, um diese beiden Probleme zu lösen.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};
James L.
quelle
Die Verwendung der ES6-Syntax macht die Verwendung jedoch eher eingeschränkt. Haben Sie eine Version, die in ES5 funktioniert?
Eitch
0

Meine Antwort war also eine Lösung, mit der ich die Variable _this verwenden kann, die auf das ursprüngliche Objekt zeigt. Ich habe eine neue Instanz eines "Quadrats" erstellt, aber ich hasste es, wie das "Quadrat" seine Größe erzeugt hat. Ich dachte, es sollte meinen spezifischen Bedürfnissen entsprechen. Zu diesem Zweck benötigte das Quadrat jedoch eine aktualisierte "GetSize" -Funktion mit den Interna dieser Funktion, die andere bereits im Quadrat vorhandene Funktionen wie this.height, this.GetVolume () aufruft. Aber dazu musste ich dies ohne verrückte Hacks tun. Hier ist meine Lösung.

Einige andere Objektinitialisierer- oder Hilfsfunktionen.

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

Funktion im anderen Objekt.

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

Wir sind uns nicht sicher, ob es unter allen Umständen funktioniert, aber in unserem Fall haben wir versucht, die describeFunktion in Jest zu überschreiben, damit wir den Namen analysieren und den gesamten describeBlock überspringen können , wenn er einige Kriterien erfüllt.

Folgendes hat bei uns funktioniert:

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

Zwei Dinge, die hier entscheidend sind:

  1. Wir verwenden keine Pfeilfunktion () =>.

    Pfeilfunktionen ändern den Verweis auf thisund wir brauchen das, um die Datei zu sein this.

  2. Die Verwendung von this.describeund this.describe.skipanstelle von nur describeund describe.skip.

Auch hier sind wir uns nicht sicher, ob es für irgendjemanden von Wert ist, aber wir haben ursprünglich versucht, mit Matthew Crumleys hervorragender Antwort davonzukommen, mussten aber unsere Methode zu einer Funktion machen und Parameter akzeptieren, um sie in der Bedingung zu analysieren.

Joshua Pinter
quelle