0

In Javascript, class variables and class methods (defined using the static keyword) are useful. When using them, I don't want to hard code the names of the classes, in case I refactor and change the name of a class. That's why class methods can get to class variables using this, and instance methods can get to class variables and class methods using this.constructor, as described in the MDN web docs.

However, it doesn't work as I'd expect when using subclasses. Each subclass gets a separate uidCounter. I want the uid to be unique across all instances of the superclass, not the subclass.

    class Test {
        static uidCounter = 1000;

        static uid() {
            this.uidCounter += 1;
            return this.uidCounter;
        }

        constructor(tag) {
            this.uid = this.constructor.uid();
        }
        
        toString() {
            return `${this.constructor.name} uid=${this.uid}`;
        }
    }

    class A extends Test {
    }

    class B extends Test {
    }


    console.clear();

    console.log(new A().toString());
    console.log(new A().toString());
    console.log(new B().toString());
    console.log(new B().toString());

It works as I intended if I hard code the name of the class:

    class Test {
        static uidCounter = 1000;

        static uid() {
            this.uidCounter += 1;
            return this.uidCounter;
        }

        constructor(tag) {
            // class name is hard coded
            this.uid = Test.uid();
        }
        
        toString() {
            return `${this.constructor.name} uid=${this.uid}`;
        }
    }

    class A extends Test {
    }

    class B extends Test {
    }


    console.clear();

    console.log(new A().toString());
    console.log(new A().toString());
    console.log(new B().toString());
    console.log(new B().toString());

Who can explain, and is there a solution that doesn't depend on hard coding the parent class name?

Answering my own question:

This ticket was closed and linked to another article, even though it doesn't answer the question. I have found a solution, which I show here:

class Test {
    static top = this;
    static uidCounter = 1000;

    static uid() {
        this.uidCounter += 1;
        return this.uidCounter;
    }

    constructor(tag) {
        // instead of hard coding the class name, use an additional class variable
        this.uid = this.constructor.top.uid();
    }
    
    toString() {
        return `${this.constructor.name} uid=${this.uid}`;
    }
}

class A extends Test {
}

class B extends Test {
}


console.clear();

console.log(new Test().toString());
console.log(new Test().toString());
console.log(new A().toString());
console.log(new A().toString());
console.log(new Test().toString());
console.log(new Test().toString());
console.log(new B().toString());
console.log(new B().toString());

Create another class variable that identifies the top of the inheritance hierarchy. This way, we solve the problem in a way that allows refactoring (e.g., changing the name of the class) without needing to find/replace.

I came up with another solution. Use an array (or an object) instead of a scalar.

class Test {
    // just use an array
    static uidCounter = [1000];

    static uid() {
        this.uidCounter[0] += 1;
        return this.uidCounter;
    }

    constructor(tag) {
        this.uid = this.constructor.uid();
    }
    
    toString() {
        return `${this.constructor.name} uid=${this.uid}`;
    }
}

class A extends Test {
}

class B extends Test {
}


console.clear();

console.log(new Test().toString());
console.log(new Test().toString());
console.log(new A().toString());
console.log(new A().toString());
console.log(new Test().toString());
console.log(new Test().toString());
console.log(new B().toString());
console.log(new B().toString());

I could explain why it works if anybody is interested. It's clear that the people still able to see this question don't care.

It's frustrating that this question has been closed and redirected because the target question does not address the fundamental issue here. In fact, just about everybody who replied both here an on the target question do not seem to understand the key points.

My solutions here are valuable to people who would want to create better structured Javascript, and they aren't going to see it unless this page gets opened again.

timkay
  • 656
  • 9
  • 15
  • the first example runs perfectly (NodeJS v14.5.0) – Randy Casburn Oct 21 '20 at 00:57
  • "*I don't want to hard code the names of the classes, in case I refactor and change the name of a class.*" - that's when you use the refactoring feature of your IDE to change the name everywhere the variable is referenced. Especially inside the class itself, this is a no-brainer. "*is there a solution that doesn't depend on hard coding the parent class name?*" - no. – Bergi Oct 21 '20 at 01:05
  • @RandyCasburn Runs without errors maybe, but doesn't have the desired results. – Bergi Oct 21 '20 at 01:06
  • @RandyCasburn , I just tried the first example in NodeJS v14.5.0, and it fails in the same way as I documented above. Could you please try again? – timkay Oct 21 '20 at 06:01
  • @Bergi, respectfully, I disagree. It's better to write code in a way that allows you to make changes in a concise manner. Think, for example, about what happens with your version tracking if you have to make the changes in 30 different places? That's much harder for somebody to digest than if there is a change in a single place. The reader is going to have to scan 30 different changes to confirm that they are all the same change. As a fallback, sure, use the IDE, but a more elegant solution is better, if it worked. Hence my question. – timkay Oct 21 '20 at 06:04
  • I'd say it's only 1 place: the class itself. And no, you can't do refactoring without changing the code. There is no other solution than to statically refer to the class. (You can do it indirectly of course with a `const Self = Test;` which then requires only a single line change, but that seems highly confusing to me.) – Bergi Oct 21 '20 at 07:40
  • @Bergi, the reason the ambiguity exists is that it's not clear what the top of the inheritance hierarchy is supposed to be. I came up with a solution that explicitly indicates the top, which is similar to what you propose, but is to create another class variable. `static top = this`, which can then be used by subclasses like `this.constructor.top.uid()` to get to the class methods without hard coding the class names. – timkay Oct 21 '20 at 17:58
  • @timkay But that doesn't work if one of the subclasses defines its own static `.top` property. You really just cannot use `this` if you want the method to be independent of its receiver. – Bergi Oct 21 '20 at 18:15
  • @Bergi, of course it doesn't work if you overload it. That's true in any circumstance and would be considered a bug. If I have a method named foo then I create a variable named foo, I won't be able to get to the function. If I have a loop variable i, and then I create a nested loop also using i, then I won't be able to get to the outer loop variable. If I have a class variable named top, and I want to access top, THEN DON'T OVERLOAD IT. You are missing the point here. I solved a legitimate problem her in a decent way, and you keep pushing people towards hair brained work arounds. – timkay Oct 22 '20 at 02:44

0 Answers0