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.