Friday 8 November 2024

Javascript Function.bind

In my previous post I talked about how in Python we have 2 different functions (functools.partial and functools.partialmethod) for using partial function application to functions or methods, due to how callables and bound methods work. A natural question is, what about JavaScript?

JavaScript comes with a Function.prototype.bind function. It allows us to bind a "this" value (thisArg) and other arguments (arg1, arg2...) to a function. When binding values to a function that does not use the "this" value (or just uses it for the 'global this') we can pass null or undefind as thisArg to bind. So Function.prototype.bind will serve us well for binding to a plain function, and for "extracting" a method from a class to use it as a function. I mean:



function format(it1, it2, it3, it4) {
    return `${it1} - ${it2} _ ${it3} . ${it4}`;
}

// function.bind works good for functions 
let fn1 = format.bind(null, "aa", "bb");
console.log(fn1("cc", "dd"));



class Formatter {
    constructor(name) {
        this.name = name;
    }

    format(it1, it2, it3, it4) {
        return `${this.name} + ${it1} - ${it2} _ ${it3} . ${it4}`;
    }
}

//and for a method that we want to "extract" from the class to use as a function 
let format2 = Formatter.prototype.format.bind({name: "Xuan"}, "aa");
console.log(format2("bb", "cc", "dd"));
 

Notice that arrow functions do not have a dynamic 'this', but what used to be called a lexical 'this' (the term seems to have fallen into disuse). They'll search for "this" in their scope chain [1]. This means that trying to bind a "this" value to an arrow function is useless, it will always search "this" in its scope chain. On the other side, binding other parameters to an arrow function is perfectly fine.



// function.bind works fine with arrow functions when binding normal parameters

let greet = (msg, fromX, toY) => `${msg} from ${fromX} to ${toY}`;
console.log(greet.name); //greet
let sayHi = greet.bind(null, "Hi");
console.log(sayHi.name); //bound greet
console.log(sayHi("Xose", "Francois"));
//Hi from Xose to Francois

// but binding "this" will not work. We get a bound function, but the "this" value will not be used by the arrow, it will continue to use the lexical this
let greet2 = (fromX, toY) => `${this.msg} from ${fromX} to ${toY}`;
let info = {msg: "Hi"};
let sayHi2 = greet2.bind(info); 
console.log(sayHi2("Xose", "Francois"));
//undefined from Xose to Francois


Using Function.prototype.bind for binding values to a method (that we'll continue to use as method, not as a function) rather than a normal function is a bit particular. So we have a class with a method that receives several parameters (apart from "this") and we want to bind some of them. Because of Function.prototype.bind expecting us to bind something as "this" we have to invoke bind passing that instance as thisArg and then attach the bound function to the instance itself. That's a bit odd, but OK for binding for a particular instance:



let formatter = new Formatter("Francois");
formatter.format = formatter.format.bind(formatter, "aa", "bb")
console.log(formatter.format("cc", "dd"));


But what if we want the parameter binding for all instances of the class, so that it works for any new instance that we create. For that our best bet is implementing our own partial function.



function partial(fn, ...trappedArgs) {
    return function(...args) {
        return fn.call(this, ...trappedArgs, ...args);
    }
}

// it works fine used with methods
Formatter.prototype.format = partial(Formatter.prototype.format, "aa", "bb")
let formatter2 = new Formatter("Francois");
console.log(formatter2.format("cc", "dd"));

//and when used with plain functions
let fn2 = partial(format, "aa", "bb");
console.log(fn2("cc", "dd"));


No comments:

Post a Comment