Search code examples
javascriptclasssubclasssuperprivate-members

Can't access private fields from within the same class


I'm trying to enhance the Map class in JavaScript by adding a TypedMap subclass that extends the Map superclass.

TypeMap.js

export default class TypedMap extends Map {
  #keyType;
  #valueType;
  constructor(keyType, valueType, entries) {
    if (entries) {
      for (let [k, v] of entries) {
        if (typeof k !== keyType || typeof v !== valueType) {
          throw new TypeError(`Wrong type for entry [${k}, ${v}]`);
        }
      }
    }

    super(entries);

    this.#keyType = keyType;
    this.#valueType = valueType;
  }

  set(key, value) {
    if (this.#keyType && typeof key !== this.#keyType) {
      throw new TypeError(`${key} is not of type ${this.#keyType}`);
    }

    if (this.#valueType && typeof value !== this.#valueType) {
      throw new TypeError(`${value} is not of type ${this.#valueType}`);
    }

    return super.set(key, value);
  }
}

main.js

import TypedMap from './TypedMap.js';

let entries = [
  [1, 2],
  [3, 4],
  [5, 6],
];

let typedMap = new TypedMap('number', 'number', entries);

Error I'm getting

Uncaught TypeError: Cannot read private member #keyType from an object whose class did not declare it
    at TypedMap.set (TypedMap.js?t=1696367023223:20:14)
    at new Map (<anonymous>)
    at new TypedMap (TypedMap.js?t=1696367023223:13:5)
    at main.js?t=1696367092683:9:16

The #keyType and #valueType fields are private but I should still access them from within the class TypedMap but somehow that doesn't happen to be the case here.

I think it has something to do with the overridden set method because I added a test method in the TypedMap class and could access the private fields.

Can anyone explain what is happening here?


Solution

  • You have the problem that you call super(entries) which calls this.set() which needs your private properties. But they aren't defined yet in this.

    The error message in Chrome is kind of misleading, in Firefox it's more meaningful:

    TypeError: can't access private field or method: object is not the right class
    

    That could mean that this isn't in a proper state ("not the right class"): the private properties are declared but not yet defined.

    On the other hand you cannot access this before calling super() to define your private props since super() actually provides a proper this with a proper prototype chain.

    So it's a deadlock. So add your entries manually.

    Btw you can remove your type checking in the constructor since it's done in set().

    class TypedMap extends Map {
        
        #keyType;
        #valueType;
        
      constructor(keyType, valueType, entries = []) {
        super();
        this.#keyType = keyType;
        this.#valueType = valueType;
        entries.forEach(entry => this.set(...entry));
      }
    
      set(key, value) {
      
        if (this.#keyType && typeof key !== this.#keyType) {
          throw new TypeError(`${key} is not of type ${this.#keyType}`);
        }
    
        if (this.#valueType && typeof value !== this.#valueType) {
          throw new TypeError(`${value} is not of type ${this.#valueType}`);
        }
        
        return super.set(key, value);
    
      }
    }
    let entries = [
      [1, 2],
      [3, 4],
      [5, 6],
    ];
    
    let typedMap = new TypedMap('number', 'number', entries);
    
    typedMap.forEach((k,v)=>console.log(JSON.stringify([k,v])));
    
    // check it throws
    new TypedMap('number', 'string', entries);