wycats / javascript-decorators

2.4k stars 127 forks source link

access decorated class from itself #79

Open mixtur opened 8 years ago

mixtur commented 8 years ago

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?
otakustay commented 8 years ago

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

mixtur commented 8 years ago

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.

otakustay commented 8 years ago

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 commented 8 years ago

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