Javascript: een functie uitbreiden

De belangrijkste reden waarom ik het wil, is dat ik mijn initialisatiefunctie wil uitbreiden.

Zoiets:

// main.js
window.onload = init();
function init(){
     doSomething();
}
// extend.js
function extends init(){
    doSomethingHereToo();
}

Dus ik wil een functie uitbreiden zoals ik een klasse uitbreid in PHP.

En ik zou het ook willen uitbreiden vanuit andere bestanden, dus ik heb bijvoorbeeld de originele init-functie in main.jsen de uitgebreide functie in extended.js.


Antwoord 1, autoriteit 100%

Met een bredere kijk op wat je eigenlijk probeert te doen en de context waarin je het doet, kunnen we je vast een beter antwoord geven dan het letterlijkeantwoord op uw vraag.

Maar hier is een letterlijk antwoord:

Als u deze functies ergens aan een eigenschap toewijst, kunt u de oorspronkelijke functie inpakken en in plaats daarvan uw vervanging op de eigenschap plaatsen:

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

Als je functies nog niet op een object staan, zou je ze daar waarschijnlijk willen plaatsen om het bovenstaande te vergemakkelijken. Bijvoorbeeld:

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

Maar er zijn betere manieren om dat te doen. Zoals bijvoorbeeld het bieden van een manier om init-functies te registreren.

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

Hier in 2020 (of eigenlijk elk moment na ~2016), kan dat wat compacter worden geschreven:

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

Antwoord 2, autoriteit 59%

Er zijn verschillende manieren om dit aan te pakken, het hangt ervan af wat je doel is, als je de functie ook gewoon en in dezelfde context wilt uitvoeren, kun je .apply():

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

Als je het wilt vervangen door een nieuwere init, ziet het er als volgt uit:

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

Antwoord 3, autoriteit 5%

De andere methoden zijn geweldig, maar ze behouden geen prototypefuncties die aan init zijn gekoppeld. Om dat te omzeilen kun je het volgende doen (geïnspireerd door de post van Nick Craver).

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

Antwoord 4, autoriteit 4%

Een andere optie zou kunnen zijn:

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

Antwoord 5, autoriteit 2%

2017+ oplossing

Het idee van functie-uitbreidingen komt van functioneel paradigma, dat standaard wordt ondersteund sinds ES6:

function init(){
    doSomething();
}
// extend.js
init = (f => u => { f(u)
    doSomethingHereToo();
})(init);
init();

Volgens de bezorgdheid van @TJCrowder over stapeldump, gaan de browsers tegenwoordig veel beter met de situatie om. Als u deze code opslaat in test.htmlen deze uitvoert, krijgt u

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

Lijn 12: de init-aanroep, Lijn 8: de init-extensie, Lijn 3: de ongedefinieerde doSomething()-aanroep.

Opmerking:Veel respect voor veteraan T.J. Crowder, die zo vriendelijk was mijn vraag vele jaren geleden te beantwoorden, toen ik een nieuweling was. Na de jaren herinner ik me nog de respectvolle houding en ik probeer het goede voorbeeld te volgen.


Antwoord 6

Dit is heel eenvoudig en ongecompliceerd. Kijk naar de code. Probeer het basisconcept achter de javascript-extensie te begrijpen.

Laten we eerst de javascript-functie uitbreiden.

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
}

U kunt deze functie uitbreiden door op de volgende manier een onderliggende functie te maken

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
}

Nu kunt u de functie Kind als volgt gebruiken,

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

We kunnen ook een Javascript-functie maken door Javascript-klassen uit te breiden, zoals deze.

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

Laten we deze klasse op deze manier uitbreiden met de functie Kind,

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

Je kunt de functie Kind weer als volgt gebruiken om een ​​vergelijkbaar resultaat te krijgen,

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

Javascript is erg makkelijke taal. We kunnen bijna alles. Veel plezier met JavaScript… Ik hoop dat ik je een idee heb kunnen geven om in jouw geval te gebruiken.


Antwoord 7

Gebruik extendFunction.js

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

Maar in uw specifieke geval is het gemakkelijker om de globale onload-functie uit te breiden:

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

Ik vind je vraag eigenlijk heel leuk, het zet me aan het denken over verschillende gebruiksscenario’s.

Voor javascript-gebeurtenissen wilt u echt handlers toevoegen en verwijderen, maar voor extendFunction, hoe kunt u later functionaliteit verwijderen? Ik zou gemakkelijk een .revert-methode kunnen toevoegen aan uitgebreide functies, zodat init = init.revert()de oorspronkelijke functie zou retourneren. Dit kan natuurlijk leiden tot behoorlijk slechte code, maar misschien kun je iets gedaan krijgen zonder een vreemd deel van de codebase aan te raken.

Other episodes