javascript-decorators icon indicating copy to clipboard operation
javascript-decorators copied to clipboard

access decorated class from itself

Open mixtur opened this issue 9 years ago • 4 comments

When you have decorated class, from inside that class should you be able to access decorated or original version of it?

For example.


const decorator = (x) => {
    console.log('test');
    return x;
};

@decorator
class A {
    foo() {
        return new A();
    }
}

const a = new A();//this invokes console.log
const b = a.foo();// maybe this should too?

mixtur avatar Oct 07 '16 10:10 mixtur

const a = new A();//this invokes console.log

I think it should not invoke console.log, decorator works in design time so console.log is invoked when you define class A but not when you intantiate it

let decorator = x => {
  console.log(1);
  return x;
};

@decorator
class A {

}

new A();
new A();

This code only log once.

For the new A() statement in foo method, it depends on how decorator is implemented:

let decorator = x => {
    x.prototype.bar = () => {
        console.log('bar');
    };
    return x;
};

@decorator
class A {
    foo() {
        let a = new A();
        a.bar();
    }
}

let a = new A();
a.foo();

This code successfully prints bar because decorator modifies the prototype of class A and add a method bar, so new A() just contains a bar method.

let decorator = x => class extends x {
    bar() {
        console.log('bar');
    }
};

@decorator
class A {
    foo() {
        let a = new A();
        a.bar();
    }
}

let a = new A();
a.bar();
a.foo();

However this should throw a TypeError when a.foo() is called because decorator returns a subclass of A so new A() does not contains method bar

To reference the actual decorated subclass in method foo you could change new A() to new this.constructor(), it always works

otakustay avatar Oct 07 '16 11:10 otakustay

I see why my example is not actualy showing the problem. Your last is better And you provided some way to access decorated class. I figured one can also apply decorator manually and have access to both versions.

const A = decorator(class B {
 a() { return new A(); }
 b() { return new B(); }
})

But still. Does anyone ever need to access original class from itself when it is decorated using @decorator syntax? If not, I think the opposite behaviour is better.

mixtur avatar Oct 07 '16 12:10 mixtur

It's pretty interesting why I believe the new A() state in foo method points the original class A but not the decorated one, in fact outside the class body the reference to A is the decorated version of class:

let decorator = x => class extends x {
    bar() {
        console.log('bar');
    }
};

@decorator
class A {
    foo() {
        console.log(A); // outputs [Function: A]
        let a = new A();
        a.bar();
    }
}

console.log(A); // outputs [Function: _class] but not [Function: A]

I think it is because:

  1. The language evaluation order requires this behavior (class body is evaluated before decorator rolls in), so it's really hard to have A inside class body be the decorated version
  2. It is much the same of named function expression so it is quite intuitive to me

For language itself, I think there should be cases where we need both the original version and decorated version of class, a good example is C# which has both virtual and non-virtual methods along with override and new keyword for methods, in javascript world, A.prototype.bar.call(this) is the non-virtual one and this.bar() is the virtual one

otakustay avatar Oct 07 '16 12:10 otakustay

To addition, there is a more tricky way to access both original and decorated version of class:

let B = @decorator class A {
    foo() {
        console.log(A); // Original one
        console.log(B); // Decorated one
    }
};

let a = new B();
a.foo();

It's the same as apply decorator manually

otakustay avatar Oct 07 '16 12:10 otakustay