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