Публикация шаблона подписчика с использованием декораторов, основанных на экземплярах 'this' всегда неопределено - PullRequest
0 голосов
/ 05 января 2019
class Observable {
    constructor() {
        this.handlers = [];
    }

    publish(value) {
        this.handlers.forEach(handler => {
            handler(value);
        });
    }

    subscribe(callback) {
        this.handlers.push(callback);
    }
}

const concreteObserver = new Observable();

 function Subscribe(observable) {
      return function functionDescriptor(target, propertyKey, descriptor) {
          observable.subscribe(target[propertyKey]);
          return descriptor;
      }
 }

 class MyClass {   

     constructor(){
        this.x = 5;
     }

     @Subscribe(concreteObserver)
     subsribeToValue(value) {     
       console.log(this.x); // undefined
     }
 }

Как видите, функция subscribe вызывается каждый раз, когда кто-то вызывает concreteObserver.publish(), однако, когда вы вызываете observable.subscribe(target[propertyKey]);, тогда this становится неопределенным.

Я также попытался переопределить метод получения дескриптора и вызвать его, но я все еще не определен. На занятиях я смог обернуть функцию, вызвав target.prototype.functionName.

Это работает, когда я знаю, как будет называться имя функции, но имя функции для @Subscribe может быть произвольным, поэтому я не могу использовать его в декораторе уровня класса, если я не использую Reflection для обнаружения всех аннотаций класс.


EDIT

Пока пробовал

observable.subscribe(target[propertyKey].bind(this));

, которая возвращает неопределенное значение, в этом случае подписка имеет правильный контекст.

observable.subscribe(data => descriptor.value.apply(this, data)); также имеет 'this' как неопределенное

descriptor.value = function(){
   console.log(this); //undefined 
}

descriptor.get = function(){
   console.log(this); //undefined
}

Решение, которое я придумал. Поскольку получить экземпляр класса можно только в декораторе классов, тогда именно здесь this может использоваться должным образом, в функции подписки я сообщаю, на какую функцию мне следует подписаться, затем в ClassDecorator я выполняю итерацию каждый метод, чтобы определить, имеют ли они __subscribeFunction в своем прототипе и, таким образом, подписаться на метод при связывании instance

class Observable {
    constructor() {
        this.handlers = [];
    }

    publish(value) {
        this.handlers.forEach(handler => {
            handler(value);
        });
    }

    subscribe(callback) {
        this.handlers.push(callback);
    }
}

const concreteObserver = new Observable();

function ClassDecorator(target) {
    const originalTarget = target;

    const Override = function (...args) {
        const instance = originalTarget.apply(this, args);

        Object.values(instance.__proto__).forEach(method => {
            const observableFunction = method.prototype.__subscribeFunction;
            if (observableFunction) {
                observableFunction.subscribe(method.bind(instance));
            }
        });
        return instance;
    };

    Override.prototype = originalTarget.prototype;
    customElements.define(elementName, target);
    return Override;
}

 function Subscribe(observable) {
      return function functionDescriptor(target, propertyKey, descriptor) {
          target[propertyKey].prototype.__subscribeFunction = observable;
      }
 }

 @ClassDecorator
 class MyClass {   

     constructor(){
        this.x = 5;
     }

     @Subscribe(concreteObserver)
     subsribeToValue(value) {     
       console.log(this.x); // 5
     }
 }

1 Ответ

0 голосов
/ 06 января 2019

Это не работает, потому что декоратор вызывается при создании самого класса, но перед созданием любого экземпляра. Поскольку экземпляра нет, не может быть this - у вас есть доступ только к прототипу, но свойства класса не относятся к прототипу (в отличие от методов).

Вы можете проверить это, используя этот пример:

function Example() {
    console.log("@Example initialized");
    return function exampleDescriptior(target, propertyKey, descriptor) {
        console.log("@Example called");
    }
}

console.log("Before declaring class");
class Test {
    @Example()
    public test() {}
}
console.log("After declaring class");

console.log("Before creating instance");
const test = new Test();
console.log("After creating instance");

console.log("Before calling method");
test.test();
console.log("After calling method");

, который дает вывод

Before declaring class
@Example initialized
@Example called
After declaring class
Before creating instance
After creating instance
Before calling method
After calling method

Тем не менее, то, что вы можете сделать, это написать еще один декоратор, примененный, скажем, к уровню класса, который проксирует конструктор. Если ваша аннотация @Subscribe хранит некоторые метаданные о прототипе, декоратор класса может затем найти его и выполнить фактическую разводку. Получается что-то вроде

@AutoSubscribe()
class MyClass {
  @Subscribe(observer)
  subscribe(value) {
    console.log(this.x);
  }
}

Работать должно быть возможно. Фактически, вы можете даже избавиться от второго декоратора, передавая конструктор из декоратора @Subscribe, но вам все равно придется хранить метаданные, которые вы можете просмотреть во время создания экземпляра.

...