首页 > 解决方案 > Automatically pass caller's scope property as argument in class constructor or method

问题描述

I have around 50 different classes that extend BaseClass. Some of them initialise internally some others. All instances have a unique id property

The goal is to know which instance initiated which (with some reference to the Caller.id) and the code for this to be in the context of BaseClass.

All initialisations follow this format, they call 'method' right after init: new ChildClass().method()

new ChildClass().method().bind(this) is not a good option in my case unfortunately

The code looks like this

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
 }
}

I want to avoid doing the following in my ParentClass.

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

or

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

But i still need my BaseClass to grab the callerId

Not sure if this is even possible :)

标签: javascripttypescript

解决方案


如果我理解正确,您需要保留对实例化此类的类的引用/链接。可能有不同的解决方案。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
  })
}

这个例子可以改进和加强,但它永远是一个hack,使用一个简单的方法_new可以立即解决你的问题。


推荐阅读