Programowe dodawanie kodu do funkcji javascript

114

Próbuję dostosować istniejącą bibliotekę JS bez modyfikowania oryginalnego kodu JS. Ten kod ładuje się w kilku zewnętrznych plikach JS, do których mam dostęp, a chciałbym zmienić jedną z funkcji zawartych w oryginalnym pliku bez kopiowania i wklejania całości do drugiego pliku JS.
Na przykład, JS poza granicami może mieć taką funkcję:

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

Chciałbym móc jakoś dołączyć lub wstawić jakiś kod JS do tej funkcji. Powodem jest przede wszystkim to, że w oryginalnym niedotykalnym JS funkcja jest dość ogromna i jeśli ten JS kiedykolwiek zostanie zaktualizowany, funkcja, którą go nadpiszę, będzie nieaktualna.

Nie jestem do końca pewien, czy jest to możliwe, ale pomyślałem, że sprawdzę.

Munzilla
źródło
3
pierwsza odpowiedź to powinno pomóc
DarkAjax
Czy potrzebujesz czegoś w rodzaju funkcji zwrotnej?
sinemetu1

Odpowiedzi:

220

Jeśli someFunctionjest dostępny globalnie, możesz buforować tę funkcję, utworzyć własną i zlecić jej wywołanie.

Więc jeśli to jest oryginał ...

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

Zrobiłbyś to ...

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

Oto skrzypce


Zauważ, że używam .applydo wywoływania funkcji buforowanej. Pozwala mi to zachować oczekiwaną wartość thisi przekazać wszystkie argumenty przekazane jako indywidualne argumenty, niezależnie od tego, ile ich było.

user1106925
źródło
10
Ta odpowiedź powinna być naprawdę najlepsza - zachowuje funkcjonalność danej funkcji ... +1 ode mnie.
Reid
17
+1 za używanie apply: to jedyna odpowiedź, która naprawdę rozwiązuje problem.
lonesomeday
2
@minitech: Co ... Nie znasz funcitonsłowa kluczowego JavaScript ? ;) Dzięki za edycję
1
@gdoron: Właśnie dodałem komentarz. Chyba że jestem teraz naprawdę zdezorientowany, nie znam żadnej przeglądarki, która nie zaakceptowałaby rzeczywistego obiektu Arguments jako drugiego argumentu .apply(). Ale jeśli byłby to obiekt podobny do tablicy, na przykład obiekt jQuery, to tak, niektóre przeglądarki
2
Taka odpowiedź, jak to się dzieje, rozwiązuje problem używania obiektów instancji do kontrolowania poszczególnych filmów z YouTube osadzonych za pośrednictwem interfejsu YouTube Iframe API. Nie masz pojęcia, jak długo próbowałem obejść fakt, że API wymaga, aby jego wywołanie zwrotne było pojedynczą funkcją globalną, gdy próbuję utworzyć klasę do obsługi danych każdego wideo w unikalny modularny sposób. Jesteś moim bohaterem dnia.
Carnix
31

najpierw zapisz rzeczywistą funkcję w zmiennej.

var oldFunction = someFunction;

następnie zdefiniuj własne:

someFunction = function(){
  // do something before
  oldFunction();
  // do something after
};
trójkąt
źródło
9
Jeśli twoja funkcja jest metodą, której będziesz musiał użyć, applyaby ją wywołać. Zobacz odpowiedź.
hugomg
Pracował dla mnie, ale potrzebne do wymiany someFunctionze window.someFunctionw powyższym kodzie. Powodem jest to, że moja funkcja została zadeklarowana w module $(document).ready()obsługi jquery .
Nelson
10

Możesz utworzyć funkcję, która wywołuje Twój kod, a następnie wywołuje funkcję.

var old_someFunction = someFunction;
someFunction = function(){
    alert('Hello');
    old_someFunction();
    alert('Goodbye');
}
Rocket Hazmat
źródło
6

Nie wiem, czy możesz zaktualizować funkcję, ale w zależności od tego, jak się do niej odwołuje, możesz w jej miejsce utworzyć nową funkcję:

var the_old_function = someFunction;
someFunction = function () {
    /* ..My new code... */
    the_old_function();
    /* ..More of my new code.. */
}
Ned Batchelder
źródło
5

Również. Jeśli chcesz zmienić lokalny kontekst, musisz odtworzyć funkcję. Na przykład:

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

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

Teraz

z() // => log: undefined

Następnie

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

I

z() // => log: 1

Ale to tylko najprostszy przykład. Nadal będzie wymagało dużo pracy, aby obsłużyć argumenty, komentarze i zwracaną wartość. Ponadto nadal istnieje wiele pułapek.
toString | plasterek | indexOf | lastIndexOf | nowa funkcja

Ivan Black
źródło
1

Wzorzec proxy (używany przez user1106925) można umieścić wewnątrz funkcji. Ten, który napisałem poniżej, działa na funkcjach spoza zakresu globalnego, a nawet na prototypach. Używałbyś tego w ten sposób:

extender(
  objectContainingFunction,
  nameOfFunctionToExtend,
  parameterlessFunctionOfCodeToPrepend,
  parameterlessFunctionOfCodeToAppend
)

W poniższym fragmencie możesz zobaczyć, jak używam funkcji rozszerzającej 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
źródło