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.