Javascript: estendi una funzione


94

Il motivo principale per cui lo voglio è che voglio estendere la mia funzione di inizializzazione.

Qualcosa come questo:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Quindi voglio estendere una funzione come estendo una classe in PHP.

E vorrei estenderlo anche da altri file, quindi ad esempio ho la funzione init originale in main.jse la funzione estesa in extended.js.



Risposte:


103

Con una visione più ampia di ciò che stai effettivamente cercando di fare e del contesto in cui lo stai facendo, sono sicuro che potremmo darti una risposta migliore rispetto alla risposta letterale alla tua domanda.

Ma ecco una risposta letterale:

Se stai assegnando queste funzioni a una proprietà da qualche parte, puoi racchiudere la funzione originale e mettere invece la tua sostituzione sulla proprietà:

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

Se le tue funzioni non sono già su un oggetto, probabilmente vorrai metterle lì per facilitare quanto sopra. Per esempio:

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

Ma ci sono tali modi migliori per farlo. Ad esempio, fornire un mezzo per registrare le initfunzioni.

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

(Molto di quanto sopra potrebbe essere scritto in modo un po 'più compatto, ma volevo usare nomi chiari come publicSymbolspiuttosto che il mio solito pubsoggetto letterale o anonimo. Puoi scriverlo in modo molto più compatto se vuoi avere funzioni anonime, ma non lo faccio. t molta cura per le funzioni anonime .)


Grazie per l'ottima risposta. Il mio problema con il secondo esempio è che potrei aver bisogno del risultato della funzione che sto estendendo.
Gerhard Davids

64

Ci sono diversi modi per farlo, dipende dal tuo scopo, se vuoi solo eseguire anche la funzione e nello stesso contesto, puoi usare .apply():

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

Se vuoi sostituirlo con uno più recente init, assomiglierà a questo:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
A volte potresti volere il .callmetodo invece di .apply. Vedi questa domanda StackOverflow.
MrDanA

@ Nick, ho trovato molto utile il tuo esempio JavaScript per estendere una funzione esistente, ma ero curioso di sapere come sarebbe stata eseguita la stessa cosa tramite jQuery?
domenica

+1 Grazie. Questo è davvero utile se vuoi patchare alcuni plugin di terze parti senza modificare il js originale.
GFoley83

1
Non sono sicuro di come usarlo con funzioni che prevedono parametri e valori restituiti.
Gerfried

5

Gli altri metodi sono ottimi ma non conservano alcuna funzione prototipo allegata a init. Per aggirare il problema puoi fare quanto segue (ispirato al post di 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;
}) ();

5

Un'altra opzione potrebbe essere:

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

0

Questo è molto semplice e diretto. Guarda il codice. Prova a cogliere il concetto di base dietro l'estensione javascript.

Per prima cosa estendiamo la funzione javascript.

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
}

È possibile estendere questa funzione creando una funzione figlio nel modo seguente

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
}

Ora puoi usare la funzione Child come segue,

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

Possiamo anche creare la funzione Javascript estendendo le classi Javascript, in questo modo.

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

Estendiamo questa classe con la funzione Child in questo modo,

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

Di nuovo puoi usare la funzione Child come segue per ottenere risultati simili,

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

Javascript è un linguaggio molto semplice. Possiamo fare quasi tutto. Buon JavaScripting ... Spero di essere stato in grado di darti un'idea da usare nel tuo caso.


-1

Usa extendedFunction.js

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

Ma nel tuo caso specifico, è più facile estendere la funzione di onload globale:

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

In realtà mi piace molto la tua domanda, mi fa pensare a diversi casi d'uso.

Per gli eventi javascript, vuoi davvero aggiungere e rimuovere gestori, ma per extentFunction, come potresti in seguito rimuovere la funzionalità? Potrei facilmente aggiungere un metodo .revert alle funzioni estese, quindi init = init.revert()restituirei la funzione originale. Ovviamente questo potrebbe portare a un codice piuttosto scadente, ma forse ti consente di fare qualcosa senza toccare una parte estranea della base di codice.

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.