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.js
i rozszerzoną w extended.js
.
javascript
function
extend
Adam Halasz
źródło
źródło
Odpowiedzi:
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
init
funkcji.// 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) } } }; })();
źródło
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(); };
źródło
.call
zamiast metody.apply
. Zobacz to pytanie StackOverflow.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; }) ();
źródło
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 );
źródło
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.
źródło
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.
źródło
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.źródło