Javascript: étendre une fonction


94

La raison principale pour laquelle je le souhaite est que je souhaite étendre ma fonction d'initialisation.

Quelque chose comme ça:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Je veux donc étendre une fonction comme j'étends une classe en PHP.

Et je voudrais également l'étendre à partir d'autres fichiers, donc par exemple, j'ai la fonction init originale dans main.jset la fonction étendue dans extended.js.



Réponses:


103

Avec une vision plus large de ce que vous essayez réellement de faire et du contexte dans lequel vous le faites, je suis sûr que nous pourrions vous donner une meilleure réponse que la réponse littérale à votre question.

Mais voici une réponse littérale:

Si vous affectez ces fonctions à une propriété quelque part, vous pouvez encapsuler la fonction d'origine et placer votre remplacement sur la propriété à la place:

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

Si vos fonctions ne sont pas déjà sur un objet, vous voudrez probablement les y placer pour faciliter ce qui précède. Par exemple:

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

Mais il y a ces meilleures façons de le faire. Comme par exemple, fournir un moyen d'enregistrer des initfonctions.

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

(Une grande partie de ce qui précède pourrait être écrite un peu plus compacte, mais je voulais utiliser des noms clairs comme publicSymbolsplutôt que mon pubsobjet littéral habituel ou anonyme. Vous pouvez l'écrire de manière beaucoup plus compacte si vous voulez avoir des fonctions anonymes, mais je ne le fais pas. t beaucoup de soin pour les fonctions anonymes .)


Merci pour une excellente réponse. Mon problème avec le deuxième exemple est que je pourrais avoir besoin du résultat de la fonction que j'étends.
Gerhard Davids

64

Il y a plusieurs façons de procéder, cela dépend de votre objectif, si vous souhaitez simplement exécuter la fonction également et dans le même contexte, vous pouvez utiliser .apply():

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

Si vous souhaitez le remplacer par un plus récent init, cela ressemblerait à ceci:

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

2
Parfois, vous voudrez peut-être la .callméthode à la place de .apply. Consultez cette question StackOverflow.
MrDanA du

@Nick, j'ai trouvé votre exemple JavaScript pour étendre une fonction existante très utile, mais j'étais curieux de savoir comment cette même chose serait faite via jQuery?
Dimanche

+1 Merci. C'est vraiment pratique si vous souhaitez patcher un plugin tiers sans modifier les js d'origine.
GFoley83

1
Je ne sais pas comment l'utiliser avec des fonctions qui attendent des paramètres et des valeurs de retour.
Gerfried

5

Les autres méthodes sont excellentes mais elles ne conservent aucune fonction prototype attachée à init. Pour contourner cela, vous pouvez faire ce qui suit (inspiré du message de 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

Une autre option pourrait être:

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

C'est très simple et direct. Regardez le code. Essayez de saisir le concept de base derrière l'extension javascript.

Commençons par étendre la fonction 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
}

Vous pouvez étendre cette fonction en créant une fonction enfant de la manière suivante

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
}

Vous pouvez maintenant utiliser la fonction enfant comme suit,

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

Nous pouvons également créer une fonction Javascript en étendant les classes Javascript, comme ceci.

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

Étendons cette classe avec la fonction Child comme ceci,

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

Encore une fois, vous pouvez utiliser la fonction enfant comme suit pour obtenir un résultat similaire,

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

Javascript est un langage très simple. On peut presque tout faire. Joyeux JavaScript ... J'espère avoir pu vous donner une idée à utiliser dans votre cas.


-1

Utilisez extendFunction.js

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

Mais dans votre cas spécifique, il est plus facile d'étendre la fonction globale onload:

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

En fait, j'aime beaucoup votre question, elle me fait réfléchir à différents cas d'utilisation.

Pour les événements javascript, vous voulez vraiment ajouter et supprimer des gestionnaires - mais pour extendFunction, comment pourriez-vous supprimer ultérieurement des fonctionnalités? Je pourrais facilement ajouter une méthode .revert aux fonctions étendues, donc init = init.revert()je retournerais la fonction d'origine. Évidemment, cela pourrait conduire à un code assez mauvais, mais peut-être que cela vous permet de faire quelque chose sans toucher à une partie étrangère de la base de code.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.