Skip to content

How to always make “this” keyword to reference the parent class (bind child methods to parent class)?

here is the simplest form of my problem:

class Service1 {
  constructor() { this.name = 'service1' }
  getThisName() { console.log('Name: ' + (this && this.name)) }
}

const service1 = new Service1();

service1.getThisName() // 'service1' => :)

function mapper(fn, ...params) {
  this.name = 'mapper';
  // ...params can be parsed or generated here
  fn(...params);
}

mapper(service1.getThisName) // undefined => :'(

I know I can fn.bind(service1) in the mapper function to solve the problem, but as fn is dynamic, I would prefer not to do that.
I have tried searching on how to get the parent class from child method but get no results.

I want mapper to be able to call a method of a class (or object) without loosing the this reference in a readable and straightforward way if possible. mapper is always called in the same context.

Is there a way in javascript to solve this problem ?


What I have tried

function mapper(fn, serviceClass) {
  fn.bind(serviceClass)();
}
mapper(service1.getThisName, service1) // OK but is not readable and seems hacky
function mapper(serviceClass, fnName) {
  serviceClass[fnName]();
}
mapper(service1, 'getThisName') // OK but autocompletion in the IDE don't work
function mapper(fn) {
  fn();
}
mapper(service1.getThisName.bind(service1)) // the "best practice" but in my case not enougth readable

Real use case context

In the real use case scenario, the mapper is called api2service. As the name suggests, it is used with expressJs to map api routes to services. Here is a simplified version of the code:

app.get(
  'get/all/users', // api endpoint
  api2service(
    userService.getAll, // getAll take filter as the first param
    ['req.query'] // req.query is the filter and is mapped AND parsed as the first param of the service function. Eg: {name: 'blah'}
  )
)

That code is repeated a lot of time and always called in the same context, that’s why I need something readable over the strict respect of good practices.

Answer

Until the bind operator proposal is implemented, there’s not much you can do about this. Apart from your attempts, you can automatically bind methods at construction time (see also https://github.com/sindresorhus/auto-bind):

function autoBind(obj) {
    let proto = Object.getPrototypeOf(obj);
    for (let k of Object.getOwnPropertyNames(proto)) {
        if (typeof proto[k] === 'function' && k !== 'constructor')
            obj[k] = proto[k].bind(obj)
    }
}

class Service1 {
    constructor() {
        this.name = 'service1'
        autoBind(this);
    }
    getThisName() { console.log('Name: ' + (this && this.name)) }
}

function mapper(fn) {
    fn();
}

let srv = new Service1
mapper(srv.getThisName)

or use a binding Proxy:

function Bound(obj) {
    return new Proxy(obj, {
        get(target, prop) {
            let el = target[prop];
            if(typeof el === 'function')
                return el.bind(target)
        }
    })
}

class Service1 {
    constructor() {
        this.name = 'service1'
    }
    getThisName() { console.log('Name: ' + (this && this.name)) }
}

function mapper(fn) {
    fn();
}

let srv = new Service1
mapper(Bound(srv).getThisName)