Javascript: rozszerz funkcję


96

Głównym powodem, dla którego tego chcę, jest to, że chcę rozszerzyć moją funkcję inicjalizacji.

Coś takiego:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

Chcę więc rozszerzyć funkcję, tak jak rozszerzam klasę w PHP.

Chciałbym go rozszerzyć również z innych plików, więc na przykład mam oryginalną funkcję init w main.jsi rozszerzoną w extended.js.



Odpowiedzi:


104

Mając szerszy pogląd na to, co faktycznie próbujesz zrobić i kontekst, w którym to robisz, jestem pewien, że moglibyśmy dać ci lepszą odpowiedź niż dosłowna odpowiedź na twoje pytanie.

Ale oto dosłowna odpowiedź:

Jeśli przypisujesz te funkcje do jakiejś właściwości gdzieś, możesz opakować oryginalną funkcję i zamiast tego umieścić jej zamiennik:

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

Jeśli twoich funkcji nie ma jeszcze na obiekcie, prawdopodobnie zechcesz je tam umieścić, aby ułatwić powyższe. Na przykład:

// In main.js
var MyLibrary = {
    init: function init() {
    }
};

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.call(MyLibrary); // Use #call in case `init` uses `this`
        doSomething();
    }
})();

Ale są na to lepsze sposoby. Na przykład zapewnienie środków do rejestracji initfunkcji.

// In main.js
var MyLibrary = (function() {
    var initFunctions = [];
    return {
        init: function init() {
            var fns = initFunctions;
            initFunctions = undefined;
            for (var index = 0; index < fns.length; ++index) {
                try { fns[index](); } catch (e) { }
            }
        },
        addInitFunction: function addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
            }
        }
    };
})();

Tutaj w 2020 roku (a właściwie w dowolnym czasie po ~ 2016 roku) można to napisać nieco bardziej zwięźle:

// In main.js
const MyLibrary = (() => {
    let initFunctions = [];
    return {
        init() {
            const fns = initFunctions;
            initFunctions = undefined;
            for (const fn of fns) {
                try { fn(); } catch (e) { }
            }
        },
        addInitFunction(fn) {
            if (initFunctions) {
                // Init hasn't run yet, remember it
                initFunctions.push(fn);
            } else {
                // `init` has already run, call it almost immediately
                // but *asynchronously* (so the caller never sees the
                // call synchronously)
                setTimeout(fn, 0);
                // Or: `Promise.resolve().then(() => fn());`
                // (Not `.then(fn)` just to avoid passing it an argument)
            }
        }
    };
})();

Dzięki za świetną odpowiedź. Mój problem z drugim przykładem polega na tym, że mogę potrzebować wyniku funkcji, którą rozszerzam.
Gerhard Davids

64

Można to zrobić na kilka sposobów, zależy to od celu, jeśli chcesz również wykonać tę funkcję i w tym samym kontekście, możesz użyć .apply():

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

Jeśli chcesz go zastąpić nowszym init, wyglądałoby to tak:

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

2
Czasami możesz chcieć .callzamiast metody .apply. Zobacz to pytanie StackOverflow.
MrDanA

@Nick, Twój przykład JavaScript do rozszerzenia istniejącej funkcji był bardzo przydatny, ale byłem ciekaw, jak to samo można zrobić za pomocą jQuery?
Sunil

+1 dzięki. Jest to bardzo przydatne, jeśli chcesz załatać jakąś wtyczkę innej firmy bez modyfikowania oryginalnego js.
GFoley83

1
Nie wiem, jak go używać z funkcjami, które oczekują parametrów i zwracają wartości.
Gerfried

5

Inne metody są świetne, ale nie zachowują żadnych prototypowych funkcji dołączonych do init. Aby to obejść, możesz wykonać następujące czynności (inspirowane postem Nicka Cravera).

(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

Inną opcją może być:

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

1

Rozwiązanie 2017+

Idea rozszerzeń funkcji wywodzi się z paradygmatu funkcjonalnego, który jest natywnie obsługiwany od wersji ES6:

function init(){
    doSomething();
}

// extend.js

init = (f => u => { f(u)
    doSomethingHereToo();
})(init);

init();

Zgodnie z obawą @ TJCrowder dotyczącą zrzutu stosu, przeglądarki radzą sobie dziś z sytuacją znacznie lepiej. Jeśli zapiszesz ten kod w pliku test.html i uruchomisz go, otrzymasz

test.html:3 Uncaught ReferenceError: doSomething is not defined
    at init (test.html:3)
    at test.html:8
    at test.html:12

Linia 12: połączenie inicjujące, Linia 8: numer wewnętrzny inicjujący, Linia 3: doSomething()połączenie nieokreślone .

Uwaga: Ogromny szacunek dla weterana TJ Crowdera, który uprzejmie odpowiedział na moje pytanie wiele lat temu, kiedy byłem nowicjuszem. Po latach wciąż pamiętam pełną szacunku postawę i staram się naśladować dobry przykład.


„Zgodnie z obawą @ TJCrowder dotyczącą zrzutu stosu, przeglądarki radzą sobie dziś znacznie lepiej”. Rzeczywiście, te ulepszenia zostały nawet skodyfikowane w specyfikacji. (I również mógłby uniknąć anonimowego problem, nawet w 2011 roku, z wymienionych wyrażeń funkcyjnych; chociaż IE robi im krzywdę w IE8 i poniżej, droga robi im krzywdę byłby nieszkodliwy żyć i uczyć :-)..)
TJ Crowder

(I dziękuję za bardzo miłe komentarze. Naprawdę doceniane!)
TJ Crowder

0

To jest bardzo proste i proste. Spójrz na kod. Spróbuj zrozumieć podstawową koncepcję rozszerzenia javascript.

Najpierw rozszerzmy funkcję 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
}

Możesz rozszerzyć tę funkcję, tworząc funkcję potomną w następujący sposób

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
}

Teraz możesz użyć funkcji Child w następujący sposób,

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

Możemy również stworzyć funkcję Javascript poprzez rozszerzenie klas Javascript, w ten sposób.

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

Rozszerzmy tę klasę o taką funkcję Child,

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

Ponownie możesz użyć funkcji Child w następujący sposób, aby uzyskać podobny wynik,

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

Javascript jest bardzo prostym językiem. Możemy zrobić prawie wszystko. Miłego skryptu JavaScripting ... Mam nadzieję, że mogłem dać ci pomysł do wykorzystania w twoim przypadku.


-1

Użyj rozszerzeniaFunction.js

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

Ale w twoim konkretnym przypadku łatwiej jest rozszerzyć globalną funkcję onload:

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

Naprawdę podoba mi się twoje pytanie, sprawia, że ​​myślę o różnych przypadkach użycia.

W przypadku zdarzeń javascript naprawdę chcesz dodawać i usuwać programy obsługi - ale w przypadku rozszerzeniaFunction, jak możesz później usunąć tę funkcję? Mógłbym łatwo dodać metodę .revert do funkcji rozszerzonych, więc init = init.revert()zwróciłbym oryginalną funkcję. Oczywiście może to prowadzić do całkiem złego kodu, ale być może pozwoli ci to zrobić bez dotykania obcej części bazy kodu.

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.