Melab
Melab

Reputation: 2852

Detect direct instances in JavaScript

A couple of years ago, I wanted figure how to create a type check for class A that returns true only for objects instantiated by new A(). For this, I wrote the class like this:

class A {
  static #instances = new WeakSet();

  static isInstance (value) {
    return A.#instances.has(value);
  }

  constructor () {
    if (new.target === A) {
      A.#instances.add(this);
    }
  }
}

Any instance of a class that extends A thus would return false when passed to A.isInstance. But, then I realized that this can be faked by using Reflect.construct(B, [], A). What I did, then, was create a proxy whose construct trap would only pass A as newTarget if it met some condition. I've been trying to recall what that condition was, but while experimenting to rediscover it, I'm beginning to think that my old solution might not've worked.

Is there some way to achieve my desired effect here?

Upvotes: -1

Views: 77

Answers (1)

Alexander Nenashev
Alexander Nenashev

Reputation: 23607

Seems there's no obvious clean way to do it

  1. You can monkey patch Reflect.construct to catch when the prototype is overridden
  2. You can try to check the call stack and look whether the caller is another constructor (should be available in the class' scope (which could be problematic, you could use the option 1 to help with it )):

class A {
  static #instances = new WeakSet();

  static isInstance (value) {
    return A.#instances.has(value);
  }

  constructor() {

    if (new.target === A) {
      try{
        throw new Error;
      }catch(e){
        // parse function names (in FF the stack is different)
        const [a, b] = e.stack.match(/\w+(?=\s*\()/gm);
        const fn = eval(b);
        if(fn?.toString().includes('extends A')) return;
      }
      A.#instances.add(this);
    }
  }
}

class B extends A{}

const b = Reflect.construct(B, [], A);
console.log(A.isInstance(b));

const a = new A;
console.log(A.isInstance(a));

Upvotes: 0

Related Questions