Javascript: Eine Funktion erweitern

93

Der Hauptgrund, warum ich es möchte, ist, dass ich meine Initialisierungsfunktion erweitern möchte.

Etwas wie das:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

Ich möchte also eine Funktion wie eine Klasse in PHP erweitern.

Und ich möchte es auch von anderen Dateien aus erweitern, so dass ich zum Beispiel die ursprüngliche Init-Funktion in main.jsund die erweiterte Funktion in habe extended.js.

Adam
quelle

Antworten:

102

Ich bin mir sicher, dass wir Ihnen eine bessere Antwort geben können als die wörtliche Antwort auf Ihre Frage.

Aber hier ist eine wörtliche Antwort:

Wenn Sie diese Funktionen irgendwo einer Eigenschaft zuweisen, können Sie die ursprüngliche Funktion umbrechen und stattdessen Ihren Ersatz in die Eigenschaft einfügen:

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

Wenn sich Ihre Funktionen noch nicht in einem Objekt befinden, möchten Sie sie wahrscheinlich dort ablegen, um die oben genannten Funktionen zu vereinfachen. Zum Beispiel:

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {};

    publicSymbols.init = init;
    function init() {
    }

    return publicSymbols;
})();

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.apply(MyLibrary); // Use #apply in case `init` uses `this`
        doSomething();
    }
})();

Aber es gibt so bessere Möglichkeiten, das zu tun. Wie zum Beispiel das Bereitstellen eines Mittels zum Registrieren von initFunktionen.

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {},
        initfunctions = [];

    publicSymbols.init = init;
    function init() {
        var funcs = initFunctions;

        initFunctions = undefined;

        for (index = 0; index < funcs.length; ++index) {
            try { funcs[index](); } catch (e) { }
        }
    }

    publicSymbols.addInitFunction = addInitFunction;
    function addInitFunction(f) {
        if (initFunctions) {
            // Init hasn't run yet, rememeber it
            initFunctions.push(f);
        }
        else {
            // `init` has already run, call it almost immediately
            // but *asynchronously* (so the caller never sees the
            // call synchronously)
            setTimeout(f, 0);
        }
    }

    return publicSymbols;
})();

(Vieles davon könnte etwas kompakter geschrieben werden, aber ich wollte eindeutige Namen wie publicSymbolsmein übliches pubsoder anonymes Objektliteral verwenden. Sie können es viel kompakter schreiben, wenn Sie anonyme Funktionen haben möchten, aber ich nicht. t viel Sorgfalt für anonyme Funktionen .)

TJ Crowder
quelle
Danke für eine tolle Antwort. Mein Problem mit dem zweiten Beispiel ist, dass ich möglicherweise das Ergebnis der Funktion benötige, die ich erweitere.
Gerhard Davids
64

Es gibt verschiedene Möglichkeiten, dies zu tun. Es hängt davon ab, was Ihr Zweck ist. Wenn Sie nur die Funktion ausführen möchten und im selben Kontext, können Sie Folgendes verwenden .apply():

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

Wenn Sie es durch ein neueres ersetzen möchten, initsieht es folgendermaßen aus:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};
Nick Craver
quelle
2
Manchmal möchten Sie vielleicht die .callMethode anstelle von .apply. Siehe diese StackOverflow-Frage.
MrDanA
@Nick, ich fand Ihr JavaScript-Beispiel zum Erweitern einer vorhandenen Funktion sehr nützlich, aber ich war neugierig, wie dasselbe mit jQuery gemacht werden würde.
Sunil
+1 Danke. Dies ist sehr praktisch, wenn Sie ein Plugin eines Drittanbieters patchen möchten, ohne das ursprüngliche js zu ändern.
GFoley83
1
Ich bin mir nicht sicher, wie ich es mit Funktionen verwenden soll, die Parameter und Rückgabewerte erwarten.
Gerfried
5

Die anderen Methoden sind großartig, aber sie behalten keine Prototypfunktionen bei, die an init angehängt sind. Um dies zu umgehen, können Sie Folgendes tun (inspiriert von dem Beitrag von Nick Craver).

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();
Verbündete
quelle
4

Eine andere Option könnte sein:

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );
Sérgio
quelle
0

Das ist sehr einfach und unkompliziert. Schauen Sie sich den Code an. Versuchen Sie, das Grundkonzept der Javascript-Erweiterung zu verstehen.

Lassen Sie uns zunächst die Javascript-Funktion erweitern.

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

Sie können diese Funktion erweitern, indem Sie die untergeordnete Funktion folgendermaßen erstellen

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

Jetzt können Sie die untergeordnete Funktion wie folgt verwenden:

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Wir können auch eine Javascript-Funktion erstellen, indem wir Javascript-Klassen wie diese erweitern.

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

Erweitern wir diese Klasse mit der Child-Funktion wie folgt:

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

Wieder können Sie die untergeordnete Funktion wie folgt verwenden, um ein ähnliches Ergebnis zu erhalten:

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript ist eine sehr einfache Sprache. Wir können fast alles machen. Happy JavaScripting ... Ich hoffe, ich konnte Ihnen eine Idee geben, die Sie in Ihrem Fall verwenden können.

Marktwarteschlange
quelle
-1

Verwenden Sie verlängernFunction.js

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

In Ihrem speziellen Fall ist es jedoch einfacher, die globale Onload-Funktion zu erweitern:

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

Ihre Frage gefällt mir wirklich gut, sie lässt mich über verschiedene Anwendungsfälle nachdenken.

Bei Javascript-Ereignissen möchten Sie wirklich Handler hinzufügen und entfernen. Wie können Sie bei ExtendFunction später Funktionen entfernen ? Ich könnte erweiterten Funktionen leicht eine .revert-Methode hinzufügen, init = init.revert()um die ursprüngliche Funktion zurückzugeben. Natürlich könnte dies zu einem ziemlich schlechten Code führen, aber vielleicht können Sie damit etwas erledigen, ohne einen fremden Teil der Codebasis zu berühren.

Devin G Rhode
quelle