Ajout de code à une fonction javascript par programmation

114

J'essaie de personnaliser une bibliothèque JS existante sans modifier le code JS d'origine. Ce code se charge dans quelques fichiers JS externes auxquels j'ai accès, et ce que j'aimerais faire est de changer l'une des fonctions contenues dans le fichier d'origine sans copier et coller le tout dans le deuxième fichier JS.
Ainsi, par exemple, le JS hors limites pourrait avoir une fonction comme celle-ci:

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

J'aimerais pouvoir en quelque sorte ajouter ou ajouter du code JS dans cette fonction. La raison est principalement que dans le JS original intouchable, la fonction est assez énorme et si ce JS est mis à jour un jour, la fonction avec laquelle je l'écrase sera obsolète.

Je ne suis pas tout à fait sûr que ce soit possible, mais j'ai pensé que je vérifierais.

Munzilla
la source
3
la première réponse de ce devrait vous aider
DarkAjax
Voulez-vous quelque chose comme une fonction de rappel?
sinemetu1

Réponses:

220

Si someFunctionest globalement disponible, vous pouvez mettre en cache la fonction, créer la vôtre et demander à la vôtre de l'appeler.

Donc, si c'est l'original ...

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

Vous feriez ça ...

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

Voici le violon


Notez que j'utilise .applypour appeler la fonction mise en cache. Cela me permet de conserver la valeur attendue de this, et de transmettre les arguments qui ont été transmis en tant qu'arguments individuels, quel que soit le nombre.

user1106925
la source
10
Cette réponse devrait vraiment être la première - elle préserve la fonctionnalité de la fonction en question ... +1 de ma part.
Reid
17
+1 pour l'utilisation apply: c'est la seule réponse qui résout vraiment le problème.
lonesomeday
2
@minitech: Quoi ... Vous n'êtes pas familier avec le funcitonmot - clé JavaScript ? ;) Merci pour la modification
1
@gdoron: Je viens d'ajouter un commentaire. À moins que je ne sois vraiment confus pour le moment, je ne connais aucun navigateur qui n'accepterait pas un objet Arguments réel comme deuxième argument .apply(). Mais s'il s'agissait d'un objet de type Array comme un objet jQuery par exemple, alors oui, certains navigateurs
2
Cette réponse, en l'occurrence, résout le problème de l'utilisation d'objets instanciés pour contrôler des vidéos YouTube individuelles intégrées via l'API YouTube Iframe. Vous n'avez aucune idée depuis combien de temps j'essaie de contourner le fait que l'API nécessite que son rappel soit une fonction globale unique lorsque j'essaie de créer une classe pour gérer les données de chaque vidéo de manière modulaire unique. Tu es mon héros du jour.
Carnix
31

stocker d'abord la fonction réelle dans une variable.

var oldFunction = someFunction;

puis définissez le vôtre:

someFunction = function(){
  // do something before
  oldFunction();
  // do something after
};
vagabonder
la source
9
Si votre fonction est une méthode que vous devrez utiliser applypour l'appeler. Voir la réponse de am.
hugomg le
Cela a fonctionné pour moi, mais devait être remplacé someFunctionpar window.someFunctionle code ci-dessus. La raison en est que ma fonction a été déclarée dans un $(document).ready()gestionnaire jquery .
Nelson le
10

Vous pouvez créer une fonction qui appelle votre code, puis appelle la fonction.

var old_someFunction = someFunction;
someFunction = function(){
    alert('Hello');
    old_someFunction();
    alert('Goodbye');
}
Fusée Hazmat
la source
6

Je ne sais pas si vous pouvez mettre à jour la fonction, mais selon la façon dont elle est référencée, vous pouvez créer une nouvelle fonction à sa place:

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

Aussi. Si vous souhaitez modifier le contexte local, vous devez recréer la fonction. Par exemple:

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

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

Maintenant

z() // => log: undefined

ensuite

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

Et

z() // => log: 1

Mais ce n'est que l'exemple le plus simple. Il faudra encore beaucoup de travail pour gérer les arguments, les commentaires et la valeur de retour. De plus, il existe encore de nombreux écueils.
toString | tranche | indexOf | lastIndexOf | nouvelle fonction

Ivan Black
la source
1

Le modèle de proxy (tel qu'utilisé par user1106925) peut être placé dans une fonction. Celui que j'ai écrit ci-dessous fonctionne sur des fonctions qui ne sont pas dans la portée globale, et même sur des prototypes. Vous l'utiliseriez comme ceci:

extender(
  objectContainingFunction,
  nameOfFunctionToExtend,
  parameterlessFunctionOfCodeToPrepend,
  parameterlessFunctionOfCodeToAppend
)

Dans l'extrait ci-dessous, vous pouvez me voir utiliser la fonction pour étendre test.prototype.doIt ().

// 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
la source