enum typeof

I'm aware that modern text editors, IDEs, linters, and even TypeScript itself already alleviate this issue considerably, but they don't quite make it. I'm talking about typeof x == STRING_LITERAL, which is such a common idiom that VScode has autocompletion enabled for the LITERAL part.

I propose that JS should have an object or enum (this one is only valid if any of the enum proposals gets to Stage 4) that enumerates all possible values that typeof could return. A userland implementation would be:

/**
 * Strings returned by `typeof`.
 * 
 * For the sake of conciseness, the property keys are shortened.
 * I would expect the keys to be equal to the values (only diff being capitalization).
 * @readonly
 * @enum {string}
 */
const TYPE = Object.freeze({
    OBJ: "object",
    UNDEF: "undefined",
    BOOL: "boolean",
    NUM: "number",
    BIGINT: "bigint",
    STR: "string",
    SYM: "symbol",
    FN: "function"
})

typeof isNaN == TYPE.FN //true

The problem is the prototype chain, so accessing arbitrary props can return proto-props. Using Object.create(null) and then assigning prevents static checking, so we can't use it. Using Map is not a good alternative, because Maps cannot be accessed using dot notation.

If this was provided as a library built-in, it would be future-proof, because if new types are added it would be auto-reflected on the enum.

TYPE doesn't need to be a global var, it could be set in an existing (or future) namespace, like Enum, or something else.

Another solution is to avoid strings altogether and use "raw enums", but that would require a new operator akin to instanceof, that compares enum values directly like this:

0 typein Enum[Symbol.NUMBER] //true

I took inspiration from Symbol.iterator

Related: Enums in Javascript with ES6 - Stack Overflow

We don’t have general purpose enum yet, but also, I’m not sure why this is a problem that needs solving. As you said, every tool in the ecosystem knows what the typeof values are - and someone not using these tools won’t get an error if they typo the enum property name, so it won’t help them.

You can set the prototype in line:

const LR = Object.freeze({
   __proto__: null,
   L: "left",
   R: "right",
});

I know, that's why we would have to wait.

I think the most important advantage is runtime detection of available types. But that's usually unnecessary, because built-in types usually come with their own literals and syntax

Thank you for the info. But, isn't __proto__ bad practice? I heard that it should be avoided, even if we're deleting it

There are two __proto__s. One is a legacy browser accessor property in Object.prototype which you shouldn't use, and then theres a special key in object initializers called __proto__ which will set the prototype of the object being created, but do so directly rather than going through the accessor. This key isn't triggered for computed properties, so if you ever want a property called "__proto__" you can still get one using a computed property within the initializer.

const o1 = {
    __proto__: { foo: true } // not the accessor
}
console.log(o1.hasOwnProperty("__proto__")) // false
console.log(Object.getPrototypeOf(o1).hasOwnProperty("foo")) // true

const o2 = {
    ["__proto__"]: { foo: true }
}
console.log(o2.hasOwnProperty("__proto__")) // true
console.log(Object.getPrototypeOf(o2).hasOwnProperty("foo")) // false

If after an object is created you refer to __proto__ it will be using the Object.prototype accessor (assuming you haven't given it its own __proto__ as seen in the second example above).

const o = {}
o.__proto__ = { foo: true } // is the accessor
console.log(o1.hasOwnProperty("__proto__")) // false
console.log(Object.getPrototypeOf(o1).hasOwnProperty("foo")) // true

IMO, the typeof operator is just broken. It treats "null" as type object, and it treats functions as a different type from objects (a function is an object, so it really doesn't make sense to have typeof myFn === 'object' not be true). Adding more features around this operator would just further establish its broken type-categorization system.

What I would really like to see, and something that's been discussed a bit elsewhere, is for the language to add a replacement to the typeof operator. Perhaps, as part of that discussion, needs involving the ability to programmatically access the different available types without hard-coding a list of types can be addressed

Though... to be honest, I've never actually ran into a concrete use-case where having a way to programmatically get my hands on all of the possible outputs of typeof would be handy, so it's hard to picture the utility of that.

Thanks for clarifying. That's seems like a very weird quirk of JS

TBF, functions are just "callable objs" which makes them "exotic" in a similar way to Arrays, but the fact that typeof [] doesn't return "array" would make even less sense, so I agree with you, it's broken.

That's one of the reasons why I suggested typein

The only I can think of, right now, off the top of my head, would be when implementing a function that checks a predicate against an arbitrary numeric value (number, bigint, potentially bigdecimal), returning false for every non-numeric.

While BigFloats/BigDecimals are not implemented, a "normal" implementation of this fn would return false for bigfloats (when they get added), despite being numeric. But a future-proof generic fn implementation may detect the existence of BigFloats at runtime and "behave properly".

But, of course, this fn can be implemented by just checking the existence of the corresponding global object. Neither global-objs nor type names are guaranteed to be the same in the future, so both approaches are equivalent... unless, we had a "type category" function or operator to get the superset name of a subset type, allowing the fn to be truly future proof, because it'll work with whatever primitive numeric type that's added in the future, without need to update the code.

This is useful, because there's no way of knowing if a global object is associated with a primitive type, unless we use typeof:

const f = x => {
  try {
    // I know fns aren't primitives
    const t = typeof x() 
    return t != 'object' ? t : undefined
  }
  catch (e) {
   //to-do
  }
}

(This reply is incomplete, I'll edit it later)
(Edit: I forgot what I was going to say...)

Now that I think about it, is it a good idea to add a new method for this to the Reflect object;

I mean typeof it a way of Reflection, so in my opinion it makes sense.
Something like

Reflect.type(x) // not sure about the api
1 Like

The Reflect namespace represents the standard internal methods of objects that form JavaScript's meta-object-protocol. ECMAScript® 2023 Language Specification

Type is a more general operation not tied to objects, so it may not follow that it would live in the Reflect namespace.

tldr; javascript needs generics if it wants to keep on its oop path

Not entirely sure what you mean? My understanding of generics is that they're a feature to aid users in creating "generic" type definitions that are capable of operating on various types. JavaScript doesn't have a concept of "type definitions". TypeScript has type definitions though, which is why TypeScript provides generic syntax.