Zastępowanie funkcji JavaScript podczas odwoływania się do oryginału


160

Mam funkcję, a()którą chcę przesłonić, ale też mam a()wykonać oryginał w kolejności zależnej od kontekstu. Na przykład czasami podczas generowania strony chcę zastąpić ją w następujący sposób:

function a() {
    new_code();
    original_a();
}

a czasami tak:

function a() {
    original_a();
    other_new_code();
}

Jak mogę to osiągnąć original_a()z over-ringu a()? Czy to w ogóle możliwe?

Proszę nie sugerować alternatyw dla over-riding w ten sposób, znam wiele. Pytam konkretnie o ten sposób.

Odpowiedzi:


179

Możesz zrobić coś takiego:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Deklaracja original_awewnątrz anonimowej funkcji zapobiega zaśmiecaniu globalnej przestrzeni nazw, ale jest dostępna w funkcjach wewnętrznych.

Podobnie jak Nerdmaster wspomniany w komentarzach, pamiętaj o umieszczeniu ()na końcu. Chcesz wywołać funkcję zewnętrzną i zapisać wynik (jedną z dwóch funkcji wewnętrznych) w a, a nie przechowywać samą funkcję zewnętrzną w a.


25
W przypadku jakichkolwiek idiotów tam jak ja - zwracać szczególną uwagę na „()” na końcu - bez tego, zwraca zewnętrzną funkcję, a nie wewnętrzne funkcje :)
Nerdmaster

@Nerdmaster Dzięki za wskazanie tego. Dodałem notatkę, aby, miejmy nadzieję, pomóc ludziom zauważyć.
Matthew Crumley,

5
Wow, próbowałem to zrobić bez przestrzeni nazw i otrzymałem przepełnienie stosu, lol.
gosukiwi

Myślę, że wynik byłby taki sam, gdyby pierwszy i ostatni nawias został usunięty z funkcji. Jak stąd (functi..i }). Samo zrobienie tego }();dałoby ten sam wynik, ponieważ (function{})()pierwszy zestaw nawiasów jest używany, ponieważ nie możesz po prostu zadeklarować anonimowego wyrażenia funkcyjnego, które musisz przypisać. Ale wykonując () nie definiujesz niczego po prostu zwracając funkcję.
Muhammad Umer

@MuhammadUmer Masz rację, że nawiasy wokół wyrażenia funkcji nie są konieczne. Uwzględniłem je, ponieważ bez nich, jeśli spojrzysz tylko na pierwsze kilka wierszy, wygląda to (przynajmniej dla mnie) tak, jakbyś przypisywał zewnętrzną funkcję bezpośrednio do a, nie wywołując jej i nie przechowując wartości zwracanej. Nawiasy informują, że dzieje się coś jeszcze.
Matthew Crumley,

88

Wzorzec Proxy może pomóc:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Powyższe zawija swój kod w funkcję, aby ukryć zmienną „zastępczą”. Zapisuje metodę setArray jQuery w zamknięciu i nadpisuje ją. Następnie proxy rejestruje wszystkie wywołania metody i deleguje wywołanie do oryginału. Użycie apply (this, arguments) gwarantuje, że wywołujący nie będzie w stanie zauważyć różnicy między metodą oryginalną a metodą proxy.


10
w rzeczy samej. użycie `` zastosuj '' i
Robert Levy

Zauważ, że ten wzorzec nie wymaga jQuery - jako przykładu używają tylko jednej z funkcji jQuery.
Kev

28

Dzięki chłopaki wzorzec proxy naprawdę pomógł ..... Właściwie to chciałem wywołać funkcję globalną foo .. Na niektórych stronach muszę zrobić kilka kontroli. Więc zrobiłem co następuje.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Dzięki temu naprawdę mi pomogło


3
Postępując zgodnie z wzorcem proxy , w niektórych przypadkach będzie ważne, aby zaimplementować ten szczegół, którego nie ma w kodzie tej odpowiedzi: Zamiast org_foo(args)wywołać org_foo.call(this, args). To zachowuje się thistak, jak by było, gdyby window.foo wywoływało normalnie (bez pośrednictwa proxy). Zobacz tę odpowiedź
cellepo

10

Możesz przesłonić funkcję używając konstrukcji takiej jak:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Na przykład:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Edycja: Poprawiono literówkę.


+1 Jest to o wiele bardziej czytelne niż inne przykłady wzorców proxy!
Mu Mind

1
... ale jeśli się nie mylę, jest to ograniczone do funkcji zeroargumentowych lub przynajmniej pewnej z góry określonej liczby argumentów. Czy istnieje sposób na uogólnienie go na dowolną liczbę argumentów bez utraty czytelności?
Mu Mind

@MuMind: tak, ale używając poprawnie wzorca proxy - zobacz moją odpowiedź .
Dan Dascalescu

4

Przekazywanie dowolnych argumentów:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
przyzwyczajenie argumenty odnoszą się original_achociaż?
Jonathan.

2

Odpowiedzią, którą zapewnia @Matthew Crumley, jest użycie natychmiast wywołanych wyrażeń funkcyjnych, aby zamknąć starszą funkcję „a” w kontekście wykonania funkcji zwróconej. Myślę, że to była najlepsza odpowiedź, ale osobiście wolałbym przekazać funkcję „a” jako argument do IIFE. Myślę, że jest to bardziej zrozumiałe.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

Powyższe przykłady nie są poprawnie stosowane thislub przekazywane argumentsdo przesłonięcia funkcji. Podkreślenie _.wrap () zawija istniejące funkcje, stosuje thisi przekazuje argumentspoprawnie. Zobacz: http://underscorejs.org/#wrap


0

Miałem kod napisany przez kogoś innego i chciałem dodać wiersz do funkcji, której nie mogłem znaleźć w kodzie. Więc jako obejście chciałem to zastąpić.

Żadne z rozwiązań nie zadziałało jednak dla mnie.

Oto, co zadziałało w moim przypadku:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

Stworzyłem małego pomocnika dla podobnego scenariusza, ponieważ często musiałem nadpisywać funkcje z kilku bibliotek. Ten pomocnik akceptuje „przestrzeń nazw” (kontener funkcji), nazwę funkcji i funkcję nadpisującą. Zastąpi oryginalną funkcję w przywołanej przestrzeni nazw nową.

Nowa funkcja przyjmuje oryginalną funkcję jako pierwszy argument, a oryginalne argumenty funkcji jako resztę. Za każdym razem zachowa kontekst. Obsługuje również funkcje void i non-void.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Użycie na przykład z Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Nie stworzyłem jednak żadnych testów wydajności. Może to prawdopodobnie dodać niechciane obciążenie, które może lub nie może być wielką sprawą, w zależności od scenariuszy.


0

Moim zdaniem pierwsze odpowiedzi nie są czytelne / możliwe do utrzymania, a pozostałe odpowiedzi nie wiążą odpowiednio kontekstu. Oto czytelne rozwiązanie wykorzystujące składnię ES6 do rozwiązania obu tych problemów.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

Ale użycie składni ES6 sprawia, że ​​jest ona raczej ograniczona w użyciu. Czy masz wersję, która działa w ES5?
eitch

0

Tak więc moja odpowiedź okazała się rozwiązaniem, które pozwala mi użyć _tej zmiennej wskazującej na oryginalny obiekt. Tworzę nową instancję „Square”, jednak nienawidzę sposobu, w jaki „Square” generuje swój rozmiar. Pomyślałem, że powinien odpowiadać moim konkretnym potrzebom. Jednak w tym celu potrzebowałem kwadratu, aby miał zaktualizowaną funkcję "GetSize" z wewnętrznymi elementami tej funkcji wywołującymi inne funkcje już istniejące w kwadracie, takie jak this.height, this.GetVolume (). Ale żeby to zrobić, musiałem to zrobić bez żadnych zwariowanych hacków. Oto moje rozwiązanie.

Inny inicjator Object lub funkcja pomocnicza.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Funkcja w innym obiekcie.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

Nie jestem pewien, czy zadziała we wszystkich okolicznościach, ale w naszym przypadku próbowaliśmy nadpisać describefunkcję w Jest, abyśmy mogli przeanalizować nazwę i pominąć całośćdescribe blok, jeśli spełnia on jakieś kryteria.

Oto, co zadziałało dla nas:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Dwie rzeczy, które są tutaj krytyczne:

  1. Nie używamy funkcji strzałek () => .

    Funkcje strzałkowe zmieniają odniesienie na thisi potrzebujemy, aby było to plik this.

  2. Użycie this.describei this.describe.skipzamiast just describeand describe.skip.

Ponownie, nie jestem pewien, czy jest to wartościowe dla nikogo, ale początkowo próbowaliśmy uciec od doskonałej odpowiedzi Matthew Crumleya, ale musieliśmy uczynić naszą metodę funkcją i zaakceptować parametry, aby przeanalizować je w warunku.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.