BTC
BTC

Reputation: 4062

Problems decorating an instance method in JavaScript

I'm having a difficult problem with decorating an instance method in ES6. I'm having no problem decorating the method, but it seems as though it is stuck with a single state of the instance of the class. Here is what I'm dealing with specifically:

class Test {
    init() {
        this.foo = 'bar';
    }

    @decorator
    decoratedMethod() {
        console.log(this.foo); // undefined
    }
}

let test = new Test();
test.init();
test.decoratedMethod();

function decorator(target, name, descriptor) {
     let fn = target[ name ].bind(target, 'a', 'b');
     return fn;
}

I realize that the code above is doing exactly what it should be, but if I want to have access to foo and other properties added to the scope how can I decorate decoratedMethod and still bind new function properties?

Upvotes: 2

Views: 949

Answers (1)

loganfsmyth
loganfsmyth

Reputation: 161457

Method decorators run once at class declaration time, not when the class is instantiated. That means the target in your example is Test.prototype, not an instance. So your example is essentially:

class Test {
  init() {
    this.foo = 'bar';
  }

  decoratedMethod() {
    console.log(this.foo); // undefined
  }
}

Test.prototype.decoratedMethod = 
    Test.prototype.decoratedMethod.bind(Test.prototype, 'a', 'b');

That should make it clear why your code is failing. The object you are binding has no foo property, only the instance does.

If you want your decorator to be processed for each instance, things get more complicated and you'd need to make the bind happen after the instance is created. One approach to that would be

function decorator(target, name, descriptor){
  const {value} = descriptor;
  delete descriptor.value;
  delete descriptor.writable;
  descriptor.get = function(){
    // Create an instance of the bound function for the instance.
    // And set an instance property to override the property
    // from the object prototype.
    Object.defineProperty(this, name, {
      enumerable: descriptor.enumerable,
      configurable: descriptor.configurable,
      value: value.bind(this, 'a', 'b'),
    });

    return this[name];
  };
}

Upvotes: 4

Related Questions