Generically apply context to method aliases

Viewed 49

I an encountering a problem which I can't solve. I don't know whether it is a lack knowledge, or the fact that it is not even possible in Javascript, but I hope to get to know it.

I am trying to execute a list of function aliases in an object. When executing these functions, I would like to use them as if they were executed right from the instance itself, So I can use other methods and instance variables within that called method. To make my explaination a bit more clear, here is an example:

class Bar {
  constructor() {
    this.name = "Bar";
  }
    
  someMethod() {
    console.log(this.name) // should log Bar
  }
}

class Foo {
  constructor() {
    this.name = "Foo";
  }

  someOtherMethod() {
    console.log(this.name) // should log Foo
  }
}

const bar = new Bar();
const foo = new Foo();


const methodList = {
  foo: bar.someMethod,
  baz: foo.someOtherMethod,
}

for(let prop in methodList) {
  methodList[prop](); // logs 2x undefined
}

for(let prop in methodList) {
  methodList[prop].apply(foo); //logs 2x Foo
}

As can be seen in the example above, this.name is a variable in the instance of the class. When executing the second loop, a context is applied and logs correctly, as expected. I would like to see that context being applied automatically, since the function alias object is executed in a different file, not knowing about foo or bar and just receiving the list.

Is there any way to achieve this?

2 Answers

You could wrap your foo and bar methods in a function of their own. Within these methods you could then call the object's method someMethod()/someOtherMethod() on the object like so:

const methodList = {
  foo: (...args) => bar.someMethod(...args),
  baz: (...args) => foo.someOtherMethod(...args),
}

At the moment your first loop doesn't work because your this doesn't refer to the actual context of the object as that isn't what is used to invoke the method. It instead refers to your methodList

See example bellow:

class Bar {
  constructor() {
    this.name = "Bar";
  }
    
  someMethod() {
    console.log(this.name) // should log Bar
  }
}

class Foo {
  constructor() {
    this.name = "Foo";
  }

  someOtherMethod() {
    console.log(this.name) // should log Foo
  }
}

const bar = new Bar();
const foo = new Foo();


const methodList = {
  foo: (...args) => bar.someMethod(...args),
  baz: (...args) => foo.someOtherMethod(...args),
}

for(let prop in methodList) {
  methodList[prop](); // correct logs
}

This happens because the contextual this when invoking methodList[prop] is methodList, hence this in someMethod and someOtherMethod is actually:

{
  foo: bar.someMethod,
  baz: foo.someOtherMethod,
}

To solve the issue, you can wrap the methods in an anonymous function returning the invoked method, as shown below:

class Bar {
  constructor() {
    this.name = "Bar";
  }
    
  someMethod(a,b,c) {
    console.log(a,b,c,this.name) // should log Bar
  }
}

class Foo {
  constructor() {
    this.name = "Foo";
  }

  someOtherMethod(a,b,c) {
    console.log(a,b,c,this.name) // should log Foo
  }
}

const bar = new Bar();
const foo = new Foo();


const methodList = {
  foo: (...args) => bar.someMethod(...args), // <-- anonymous function that, once invoked, returns `bar.someMethod()`, hence the contextual `this` of someMethod will be `bar`.
  baz: function() {  // <-- same as above, just written without the lambda notation.
     return foo.someOtherMethod(...arguments);
  }//^
}//  | <-- that evaluation is actually calling the above code block.
//   |-------------------------------------|
for(let prop in methodList) {//            |
  methodList[prop](1,4,'hello'); // <------|
}

Related