Автоматически передавать свойство области видимости вызывающего абонента в качестве аргумента в конструкторе или методе класса

#javascript #typescript

Вопрос:

У меня есть около 50 различных классов, которые расширяют базовый класс. Некоторые из них инициализируют внутренне некоторые другие. Все экземпляры обладают уникальным id свойством

Цель состоит в том, чтобы знать, какой экземпляр инициировал какой (с некоторой ссылкой на Caller.id) и код для этого должен быть в контексте базового класса.

Все инициализации следуют этому формату, они вызывают «метод» сразу после инициализации: new ChildClass().method()

новый класс детей().метод().привязка(это), к сожалению, не лучший вариант в моем случае

Код выглядит так

 class ChildClass extends BaseClass {
 id = 'ChildId';
}

class ParentClass extends BaseClass{
 id = 'ParentId';
 parentMethod() {
  new ChildClass().method()
 }
}

abstract class BaseClass {
 protected id;
 private callerId;

 constructor() {
 // either here we set callerId from params
 }
 method() {
  // or here we set callerId from params
 }
}
 

Я хочу избежать следующего в моем классе родителей.

 const callerId = this.id;
new ChildClass(callerId).method()
 

или

 const callerId = this.id;
new ChildClass().method(callerId)
 

Но мне все еще нужен мой базовый класс, чтобы захватить идентификатор вызывающего абонента

Не уверен, что это вообще возможно 🙂

Комментарии:

1. Нет его javascript/машинописный текст 🙂

2. Вы говорите из парамов, но вы не проходите никаких парамов. Не совсем уверен, в чем заключается ваш вопрос здесь

3. В реальном коде я передаю кучу параметров, как при инициализации, так и при самом вызове метода. Вопрос в том, как НЕ передать идентификатор вызывающего абонента = this.id, но базовый класс должен каким-то образом захватить его автоматически. Родитель и ребенок здесь просто условности. У меня есть около 50 различных классов, которые расширяют базовый класс. Некоторые из них инициализируют внутренне некоторые другие. Все 50 содержат this.id Я просто хочу знать, какой экземпляр инициализировал какой экземпляр

4. Этот вопрос очень неясен.

5. Извините за это, не уверен, как сделать это более конкретным.

Ответ №1:

Если я вас правильно понял, вам нужно сохранить ссылку/ссылку на класс, который создал этот класс. Могут быть разные решения. Самое простое, что я предвижу, — это заменить new в контексте вашего класса:

 type Constructor<T> = { new (...args): T, prototype: T };

abstract class BaseClass {
  protected id;
  private callerId;

  _new<T extends BaseClass>(clazz: Constructor<T>, ...args): T {
    const i = new clazz(...args);
    i.callerId = this.id;
    return i;
  }

  method() {
  }
}

class ChildClass extends BaseClass {
  id = 'ChildId';
}

class ParentClass extends BaseClass{
  id = 'ParentId';
  parentMethod() {
    this._new(ChildClass).method();
  }
}
 

Я не полностью осведомлен обо всех вуду, которые можно сделать с помощью последних версий JS/TS. Насколько мне известно, вы ничего не можете сделать, если хотите сохранить свой код в форме:

 new ChildClass().method();
 

Почему? Что ж, швейцарский нож для добавления магии в ваш JS-код есть Monkey patching . Но по замыслу при использовании new создается новый объект и передается функции конструктора в качестве текущего this контекста. Такое поведение заставляет нас терять исходную ссылку, но ее нельзя изменить, следовательно, нет решения.

Я лгал

В JS вы можете подчинять правила своей воле (вот почему многие разработчики ненавидят это. Не я, конечно). Одно очень необычное решение, которое приходит мне в голову, вдохновлено Vue.js ядро привязки ценности, и оно будет работать следующим образом:

 let __BASE_CLASS_ACT_ID = undefined; // (1)

abstract class BaseClass {
  protected id;
  private callerId;

  constructor() {
    // Catch my caller according to the current context
    this.callerId = __BASE_CLASS_ACT_ID;
    // Patch all my methods
    for (const name of Object.getOwnPropertyNames(this)) {
      if (typeof this[name] === 'function') {
        const _old = this[name]; // (3)
        this[name] = (...args) => { // (2)
          __BASE_CLASS_ACT_ID = this.id;
          _old.apply(this, args);
          __BASE_CLASS_ACT_ID = undefined;
        }
      }
    }
  }
}
 

Здесь мы используем тот факт, что JS-это язык с одним потоком, поэтому любая функция будет вызываться одновременно. Мы сохраняем глобальную переменную (1) , которая обновляется в функции исправления (2) , которая устанавливает ее перед вызовом исходной функции (3) и сбрасывает ее после. Это должно позволить вам заставить это работать:

 parentMethod() {
  new ChildClass().method();
}
 

Обратите внимание, что это не будет работать в конструкторе:

 constructor() {
  super();
  new ChildClass().method(); // not working
}
 

Также он не будет работать в асинхронной функции:

 parentMethod() {
  doSome().then(x => {
    new ChildClass().method(); // not working
  })
}
 

Этот пример можно улучшить и усилить, но он всегда останется хак, где использование простого _new решает ваши проблемы сразу.

Комментарии:

1. Спасибо за действительно полезные идеи, я узнал здесь несколько вещей. Пока _new что подход выглядит лучше. Это method действительно асинхронно, но это не единственная причина, по которой глобальное свойство не будет работать в моем случае использования, но в других случаях оно является надежным. Различные родительские классы также инициализируются параллельно, и, что еще хуже new ChildClass , код обычно используется в других асинхронных функциях. Глобалы-странная тема, но использование однопоточного выполнения и природы компилятора, на мой взгляд, не является взломом. 😀