0
\$\begingroup\$

I am looking to write a JavaScript class BasicType with five fixed instances INT, FLOAT, STRING, BOOL, ANYTHING. It should be impossible to create any additional instances.

The instances themselves should have a single enumerable, non-configurable, non-writable property called name. Printing these instances with console.log, which delegates to util.inspect, should show the class name and name, for example:

> console.log(BasicType.BOOL)
BasicType { name: 'bool' }

> BasicType.BOOL.name = "zoolean"
TypeError: Cannot assign to read only property 'name' of object '#<BasicType>'

> new BasicType("int")
Error: Basic type objects cannot be constructed

I've managed to make this work! Unfortunately my code is long and verbose and repetitive:

export class BasicType {
  static INT = Object.create(BasicType.prototype, {
    name: { value: "int", enumerable: true },
  });
  static FLOAT = Object.create(BasicType.prototype, {
    name: { value: "float", enumerable: true },
  });
  static BOOL = Object.create(BasicType.prototype, {
    name: { value: "bool", enumerable: true },
  });
  static STRING = Object.create(BasicType.prototype, {
    name: { value: "string", enumerable: true },
  });
  static ANYTHING = Object.create(BasicType.prototype, {
    name: { value: "anything", enumerable: true },
  });
  constructor() {
    throw new Error("Basic type objects cannot be constructed");
  }
}

The feedback I am looking for is:

  • Is there a way to iterate through the five values? I don't see how since I have to declare the static properties. Maybe defineProperties (though this works with a single object) or maybe there is a static initializer construct?
  • Is the throwing an error in the constructor the acceptable way to do this in JavaScript? Or is the very idea of fixing the number of instances antithetical to JavaScript?
  • Is the class construct correct here? Maybe they should just be objects? I thought of this but then the util.inspect would have to be hacked to make it look like all the objects in the enclosing application (yes, it is a compiler), and doing type analysis with .constructor also would not work without hacking. So I kind of like using class here, but if there is a better way, I would love to see it.

I know this is ES2022 but Node and modern browsers already support static and I am fine using it. Any ES2022 forms are appreciated!

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I do not understand the purpose of this object BasicType.

You ask.

"Is there a way to iterate through the five values? "

Yes using Object.entries(BasicType)

"Is the throwing an error in the constructor the acceptable way to do this in JavaScript?"

No. In JS or any other language. Don't create a thing that has no purpose.

"Is the class construct correct here?"

No, though I am unsure as to the purpose of BasicType

"Maybe they should just be objects? ... then the util.inspect would have to be hacked... ...and doing type analysis with .constructor also would not work without hacking..."

Yes class defines a function (typeof BasicType === "function") and a function is an object.

Why would inspect need to be hacked? util.inspect inspects objects. And I don't know why you use a constructor to analysis type.

You can create a more robust object that can not be modified directly using Object.freeze

Example

In example I create a class BasicType and a similar object called BType. They look and act similarly however BType can not be changed while BasicType can not be trusted as the class syntax provides no security.

In the example I make BType a function. This is just to provide a similar constructor. Ideally if you did not need a constructor you would make the BType an object. see BTypeO

class BasicType {
  static INT = Object.create(BasicType.prototype, {name: { value: "int", enumerable: true }});
  static FLOAT = Object.create(BasicType.prototype, {name: { value: "float", enumerable: true }});
  constructor() { throw new Error("Basic type objects cannot be constructed"); }
}

const BType = (()=> {
    const name = (name) => Object.freeze({name: name.toLowerCase()})
    return Object.freeze( Object.assign(function (){ throw new Error("Foo bar") },  
        ["INT","FLOAT"].reduce((obj, type) => (obj[type] = name(type), obj), {}
    )));
})();

// As typeof object. Example only
const BTypeO = (()=> {
    const name = (name) => Object.freeze({name: name.toLowerCase()})
    return Object.freeze(["INT","FLOAT"].reduce((obj, type) => (obj[type] = name(type), obj), {}));
})();

// Proxy handler to replace unprotected identifiers (eg BasicType)
const proxyHdlr = {get(t, p) {console.log("Proxy trapping ", p); return {name: "foo"}}};

console.log("--General properties BasicType, BType------------------------");
console.log("BasicType is typeof " + typeof BasicType);
console.log("BType is typeof " + typeof BType);

console.log("Iterate BasicType", Object.entries(BasicType) + "");
console.log("Iterate BType", Object.entries(BType) + "");

try{ new BasicType() } catch(e) { console.log("BasicType constructor error: " + e.message) }
try{ new BType() } catch(e) { console.log("BType constructor error: " + e.message) }


console.log("--Accessing BasicType------------------------");
BasicType.INT.name =  "monkeys";
console.log("BasicType.INT.name = " + BasicType.INT.name);  // Still here
delete BasicType.INT;  // can delete
try{ console.log(BasicType.INT.name) } catch(e){ console.log(e.message) }; // will throw error as INT is undefined
BasicType.INT = {name: "monkey"};  // can modify / replace / alter
console.log("BasicType.INT.name = " + BasicType.INT.name);
BasicType = new Proxy(BasicType, proxyHdlr);  // class name is writable and can silently be trapped
console.log("BasicType.FLOAT.name = " + BasicType.FLOAT.name);  // all access is now named foo

console.log("--Accessing BType------------------------");
BType.INT.name =  "monkeys";  // can not rename 
console.log("BType.INT.name = " + BType.INT.name);  // Still here
delete BType.INT;     // can not delete
console.log("BType.INT.name = " + BType.INT.name);  // Still here
BType.INT = {name: "monkey"};  // can not assign
console.log("BType.INT.name = " + BType.INT.name);  // Still here
try{ BType = new Proxy(BType, proxyHdlr)} catch(e){ console.log("assign to BType error: " + e.message) }; // is const and can not overwrite
console.log("BType.FLOAT.name = " + BType.FLOAT.name);

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.