Programmgesteuertes Hinzufügen von Code zu einer Javascript-Funktion

113

Ich versuche, eine vorhandene JS-Bibliothek anzupassen, ohne den ursprünglichen JS-Code zu ändern. Dieser Code wird in einige externe JS-Dateien geladen, auf die ich Zugriff habe. Ich möchte eine der in der Originaldatei enthaltenen Funktionen ändern, ohne das Ganze zu kopieren und in die zweite JS-Datei einzufügen.
So könnte beispielsweise das Off-Limits-JS eine Funktion wie die folgende haben:

var someFunction = function(){
    alert("done");
}

Ich möchte in der Lage sein, JS-Code irgendwie an diese Funktion anzuhängen oder voranzustellen. Der Grund ist in erster Linie, dass die Funktion in der ursprünglichen unberührbaren JS ziemlich groß ist und wenn diese JS jemals aktualisiert wird, ist die Funktion, mit der ich sie überschreibe, veraltet.

Ich bin mir nicht ganz sicher, ob dies möglich ist, aber ich dachte, ich würde es überprüfen.

Munzilla
quelle
3
Die erste Antwort darauf sollte Ihnen helfen
DarkAjax
Möchten Sie so etwas wie eine Rückruffunktion?
sinemetu1

Antworten:

217

Wenn someFunctiones global verfügbar ist, können Sie die Funktion zwischenspeichern, eine eigene erstellen und von Ihrer Funktion aufrufen lassen.

Also, wenn dies das Original ist ...

someFunction = function() {
    alert("done");
}

Du würdest das tun ...

someFunction = (function() {
    var cached_function = someFunction;

    return function() {
        // your code

        var result = cached_function.apply(this, arguments); // use .apply() to call it

        // more of your code

        return result;
    };
})();

Hier ist die Geige


Beachten Sie, dass ich .applydie zwischengespeicherte Funktion aufrufe. Auf diese Weise kann ich den erwarteten Wert von beibehalten thisund alle übergebenen Argumente als einzelne Argumente übergeben, unabhängig davon, wie viele vorhanden waren.

Benutzer1106925
quelle
10
Diese Antwort sollte wirklich die beste sein - sie bewahrt die Funktionalität der fraglichen Funktion ... +1 von mir.
Reid
17
+1 für die Verwendung apply: Dies ist die einzige Antwort, die das Problem wirklich löst.
einsamer
2
@minitech: Was ... Sie kennen das funcitonSchlüsselwort von JavaScript nicht ? ;) Danke für die Bearbeitung
1
@gdoron: Ich habe gerade einen Kommentar hinzugefügt. Sofern ich gerade nicht wirklich verwirrt bin, kenne ich keinen Browser, der ein tatsächliches Argument-Objekt nicht als zweites Argument akzeptiert .apply(). Aber wenn es ein Array-ähnliches Objekt wäre, wie zum Beispiel ein jQuery-Objekt, dann würden einige Browser einen Fehler
2
Diese Antwort löst zufällig das Problem der Verwendung instanziierter Objekte zur Steuerung einzelner YouTube-Videos, die über die YouTube Iframe-API eingebettet sind. Sie haben keine Ahnung, wie lange ich versucht habe, die Tatsache zu umgehen, dass die API einen Rückruf als einzelne globale Funktion erfordert, wenn ich versuche, eine Klasse zu erstellen, die die Daten jedes Videos auf einzigartige modulare Weise verarbeitet. Du bist mein Held für diesen Tag.
Carnix
31

Speichern Sie zuerst die eigentliche Funktion in einer Variablen.

var oldFunction = someFunction;

dann definieren Sie Ihre eigenen:

someFunction = function(){
  // do something before
  oldFunction();
  // do something after
};
Driangle
quelle
9
Wenn Ihre Funktion eine Methode ist, müssen Sie applysie aufrufen. Siehe ams Antwort.
Hugomg
Es funktionierte für mich, aber nötig ersetzen someFunctionmit window.someFunctionin dem obigen Code. Der Grund dafür ist, dass meine Funktion in einem JQuery- $(document).ready()Handler deklariert wurde .
Nelson
10

Sie können eine Funktion erstellen, die Ihren Code aufruft und dann die Funktion aufruft.

var old_someFunction = someFunction;
someFunction = function(){
    alert('Hello');
    old_someFunction();
    alert('Goodbye');
}
Rakete Hazmat
quelle
6

Ich weiß nicht, ob Sie die Funktion aktualisieren können, aber je nachdem, wie auf sie verwiesen wird, können Sie an ihrer Stelle eine neue Funktion erstellen:

var the_old_function = someFunction;
someFunction = function () {
    /* ..My new code... */
    the_old_function();
    /* ..More of my new code.. */
}
Ned Batchelder
quelle
5

Ebenfalls. Wenn Sie den lokalen Kontext ändern möchten, müssen Sie die Funktion neu erstellen. Beispielsweise:

var t = function() {
    var a = 1;
};

var z = function() {
    console.log(a);
};

Jetzt

z() // => log: undefined

Dann

var ts = t.toString(),
    zs = z.toString();

ts = ts.slice(ts.indexOf("{") + 1, ts.lastIndexOf("}"));
zs = zs.slice(zs.indexOf("{") + 1, zs.lastIndexOf("}"));

var z = new Function(ts + "\n" + zs);

Und

z() // => log: 1

Dies ist jedoch nur das einfachste Beispiel. Es wird noch viel Arbeit erfordern, um mit Argumenten, Kommentaren und Rückgabewerten umzugehen. Darüber hinaus gibt es noch viele Fallstricke.
toString | Scheibe | indexOf | lastIndexOf | neue Funktion

Ivan Black
quelle
1

Das Proxy-Muster (wie von user1106925 verwendet) kann in eine Funktion eingefügt werden. Die unten beschriebene Funktion bezieht sich auf Funktionen, die nicht im globalen Bereich liegen, und sogar auf Prototypen. Sie würden es so verwenden:

extender(
  objectContainingFunction,
  nameOfFunctionToExtend,
  parameterlessFunctionOfCodeToPrepend,
  parameterlessFunctionOfCodeToAppend
)

Im folgenden Snippet können Sie sehen, wie ich die Funktion zum Erweitern von test.prototype.doIt () verwende.

// allows you to prepend or append code to an existing function
function extender (container, funcName, prepend, append) {

    (function() {

        let proxied = container[funcName];

        container[funcName] = function() {
            if (prepend) prepend.apply( this );
            let result = proxied.apply( this, arguments );
            if (append) append.apply( this );
            return result;
        };

    })();

}

// class we're going to want to test our extender on
class test {
    constructor() {
        this.x = 'instance val';
    }
    doIt (message) {
        console.log(`logged: ${message}`);
        return `returned: ${message}`;
    }
}

// extends test.prototype.doIt()
// (you could also just extend the instance below if desired)
extender(
    test.prototype, 
    'doIt', 
    function () { console.log(`prepended: ${this.x}`) },
    function () { console.log(`appended: ${this.x}`) }
);

// See if the prepended and appended code runs
let tval = new test().doIt('log this');
console.log(tval);

pwilcox
quelle