Javascript: Extend a Function

JavascriptFunctionExtend

Javascript Problem Overview


The main reason why I want it is that I want to extend my initialize function.

Something like this:

// main.js

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

// extend.js

function extends init(){
    doSomethingHereToo();
}

So I want to extend a function like I extend a class in PHP.

And I would like to extend it from other files too, so for example I have the original init function in main.js and the extended function in extended.js.

Javascript Solutions


Solution 1 - Javascript

With a wider view of what you're actually trying to do and the context in which you're doing it, I'm sure we could give you a better answer than the literal answer to your question.

But here's a literal answer:

If you're assigning these functions to some property somewhere, you can wrap the original function and put your replacement on the property instead:

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

If your functions aren't already on an object, you'd probably want to put them there to facilitate the above. For instance:

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

But there are better ways to do that. Like for instance, providing a means of registering init functions.

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

Here in 2020 (or really any time after ~2016), that can be written a bit more compactly:

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

Solution 2 - Javascript

There are several ways to go about this, it depends what your purpose is, if you just want to execute the function as well and in the same context, you can use .apply():

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

If you want to replace it with a newer init, it'd look like this:

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

Solution 3 - Javascript

The other methods are great but they don't preserve any prototype functions attached to init. To get around that you can do the following (inspired by the post from 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;
}) ();

Solution 4 - Javascript

Another option could be:

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

Solution 5 - Javascript

2017+ solution

The idea of function extensions comes from functional paradigm, which is natively supported since ES6:

function init(){
    doSomething();
}

// extend.js

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

init();

As per @TJCrowder's concern about stack dump, the browsers handle the situation much better today. If you save this code into test.html and run it, you get

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

Line 12: the init call, Line 8: the init extension, Line 3: the undefined doSomething() call.

Note: Much respect to veteran T.J. Crowder, who kindly answered my question many years ago, when I was a newbie. After the years, I still remember the respectfull attitude and I try to follow the good example.

Solution 6 - Javascript

This is very simple and straight forward. Look at the code. Try to grasp the basic concept behind javascript extension.

First let us extend javascript function.

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
}

You can extend this function by creating child function in following way

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
}

Now you can use Child function as follows,

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

We can also create Javascript Function by extending Javascript classes, like this.

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

Let us extend this class with Child function like this,

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

Again you can use Child function as follows to get similar result,

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

Javascript is very easy language. We can do almost anything. Happy JavaScripting... Hope I was able to give you an idea to use in your case.

Solution 7 - Javascript

Use extendFunction.js

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

But in your specific case, it's easier to extend the global onload function:

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

I actually really like your question, it's making me think about different use cases.

For javascript events, you really want to add and remove handlers - but for extendFunction, how could you later remove functionality? I could easily add a .revert method to extended functions, so init = init.revert() would return the original function. Obviously this could lead to some pretty bad code, but perhaps it lets you get something done without touching a foreign part of the codebase.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAdam HalaszView Question on Stackoverflow
Solution 1 - JavascriptT.J. CrowderView Answer on Stackoverflow
Solution 2 - JavascriptNick CraverView Answer on Stackoverflow
Solution 3 - JavascriptAllyView Answer on Stackoverflow
Solution 4 - JavascriptpsergiocfView Answer on Stackoverflow
Solution 5 - JavascriptJan TuroňView Answer on Stackoverflow
Solution 6 - JavascriptMarket QueueView Answer on Stackoverflow
Solution 7 - JavascriptDevin RhodeView Answer on Stackoverflow