-1

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?

11
  • 1
    "I realized that this can be faked" - I'm pretty sure that whatever solution you come up with it can be faked - if nothing else by simply overwriting A.isInstance. So what's your concrete requirements, what's your actual use case? What goal do you try to achieve by checking "only for objects instantiated by new A()"? Commented Jun 11, 2024 at 2:11
  • @Bergi Whether they change A.isInstance or not is outside the scope of the question. Pretend the class is frozen, pretend it's a global variable, pretends it's preserved in behind-the-scenes code like Node's library, and so on. Commented Jun 11, 2024 at 4:09
  • 1
    So what's wrong with extending your class? How is new (class B extends A {}) different than new function B() { return new A() }? Why do you need to prevent it? Commented Jun 11, 2024 at 11:04
  • 1
    So what about Reflect.constructor(A, [])? Do you consider that to not be created by new A() either? Again, why do you care? A new B() is indistinguishable from a new A() whose prototype chain was messed with or that got extra properties added. Commented Jun 13, 2024 at 20:03
  • 1
    That thing is pretty much the same as a private field. The isInstance code in your question does work for that criterion already. "I realized that this can be faked by using Reflect.construct(B, [], A)" - only if B calls A (e.g. via super()). Same for subclasses of Set. Commented Jun 20, 2024 at 1:09

1 Answer 1

0

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));

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.