Saturday, 26 March 2022

Self-Referencing function in JavaScript

Long time ago it was possible for a function in JavaScript to reference itself by means of arguments.callee. As you can read here, this is not allowed in strict mode and should be avoided in general. You can read in that same url the explanation of why arguments.callee is no longer considered necessary. OK, I agree for the most part, but I still think that there are situations where a function should be able to reference itself in some way.

The obvious case is when you want to print in your logs the function name without hardcoding that function name (either the string or the "function.name" access). Also, in recursive functions it can be useful to call the function using a self-reference rather than the function name. Let's see an example


function recursivePrint(txt, times){
    if (times > 0){
        console.log(`v1: ${txt}`);
        recursivePrint(txt, --times);
    }
    else{
        console.log("------------");
    }
}

recursivePrint("Francoisi", 3);


p1 = recursivePrint;
p1("Francois", 3);
// v1: Francois
// v1: Francois
// v1: Francois
// ------------



//modify recursivePrint to print v2
recursivePrint = function (txt, times) {
    if (times > 0){
        console.log(`v2: ${txt}`);
        recursivePrint(txt, --times);
    }
    else{
        console.log("------------");
    }

};

p1("Francois", 3);
// v1: Francois
// v2: Francois
// v2: Francois
// ------------


In the above example p1 references to recursivePrint. Then we redefine recursivePrint. p1 continues to point to the initial recursivePrint, but in the recursion, as we are calling the function through the global reference, the ensuing calls use the redefined function. So we have a mix of v1 and v2.

Another use for self-referencing functions is if we want to attach data to the function as properties in the function, and use them from inside the function. It's similar to having a closure, in both cases we have a function with state, the main difference is that in a closure accessing to that state from outside is not possible (unless you use some odd technique like the one that that I describe here). So, I'm talking about this:


function formatter(txt){
    console.log(`v1: ${formatter.pre}txt${formatter.post}`);
}
formatter.pre = "[";
formatter.post = "]";

f = formatter;
f("hi");
//v1: [txt]
formatter = (txt) => {
    console.log(`v2: ${formatter.char}txt${formatter.char}`);
}
formatter.char = "|"

//It's a mess here, we continue to point to the original function, but it tries to get the state from the new function
f("hi");
//v1: undefinedtxtundefined

A solution for me would be having a variable inside the function that points to the function itself. We could use the name self for that variable. We can use closures for that, something like this:


function createSelfReferencingFormatterFunction(){
    let self = function formatter(txt){
        console.log(`my name is: ${self.name}`);
        console.log(`${self.pre}txt${self.post}`);
    };
    return self;
}

let formatter = createSelfReferencingFormatterFunction();
formatter.pre = "[";
formatter.post = "]";
formatter("hi");

// my name is: formatter
// [txt]

That looks pretty nice to me, but having to define a factory function for each function that we want to do self-referencing is not nice. How could we generalize it to have a generic function that receives a function and returns a self-referencing funtion?

I've come up with a solution that leverages the unfamous eval function. You can check this post from some years ago where I explain how eval has access to the scope of the function where it's invoked, in such a way that if we define a new function in the string passed to eval, that new function adds that scope to its scope chain.




function createSelfReferencingFunction(fn){
    //we have to use var, not let
    code = "var self = " + fn.toString() + ";"
    eval(code);
    return self;
}

function formatter2(txt){
    console.log(`my name is: ${self.name}`);
    console.log(`${self.pre}txt${self.post}`);
}

f1 = createSelfReferencingFunction(formatter2);
f1.pre = "[";
f1.post = "]";
f1("hi");

// my name is: formatter2
// [txt] 

Notice the comment in createSelfReferencingFunction where I indicate that let does not work and we have to use var. The reason for this is that eval creates a block, so the scope of let is restricted to that block, while var's scope is the whole function. There's an answer in stackoverflow explaining it.

Because eval introduces a new block of code. The declaration using var will declare a variable outside of this block of code, since var declares a variable in the function scope.

let, on the other hand, declares a variable in a block scope. So, your b variable will only be visible in your eval block. It's not visible in your function's scope.

No comments:

Post a Comment