DEV Community

Siddharth
Siddharth

Posted on

A better `typeof`

The typeof operator is a really useful one but it has a few pitfalls:

typeof ["an", "array"] // object
typeof /regex/g // object
typeof null // object
typeof NaN // number
typeof Number('I am not a number!') // number
Enter fullscreen mode Exit fullscreen mode

Ok, that's a lot of pitfalls;

But there is a way to get more detailed types using Object.prototype.toString.call() on a value:

// This statement basically means: "Call the toString method of the object prototype on whatever value you like"
Object.prototype.toString.call({ object: "true" }) // the infamous [object Object]
Object.prototype.toString.call(["an", "array"]) // [object Array]
Object.prototype.toString.call("a string") // [object String]
Object.prototype.toString.call(1n) // [object Bigint]
Object.prototype.toString.call(new Date()) // [object Date] really
Object.prototype.toString.call(new Error("an error")) // [object Error]
Object.prototype.toString.call(function () {}) // [object Function]
Object.prototype.toString.call(function* () {}) // [object GeneratorFunction]
Object.prototype.toString.call(/regex/gi) // [object RegExp]
Object.prototype.toString.call(Symbol()) // [object Symbol]
Object.prototype.toString.call(NaN) // it's not perfect: [object Number]
Enter fullscreen mode Exit fullscreen mode

Of course, this could be made a function (with a few finishing touches from here)

  function type(obj, showFullClass) {

    // Whether to return the whole type
    if (showFullClass && typeof obj === "object") {
        return Object.prototype.toString.call(obj);
    }

    if (obj == null) { return (obj + '').toLowerCase(); } // implicit toString() conversion

    // Removed, see comments
    // if (isNaN(+obj)) return "nan";
    if (Object.is(obj, NaN)) return "nan";

    var deepType = Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
    if (deepType === 'generatorfunction') { return 'function' }

    // Prevent overspecificity (for example, [object HTMLDivElement], etc).
    // Account for functionish Regexp (Android <=2.3), functionish <object> element (Chrome <=57, Firefox <=52), etc.
    // String.prototype.match is universally supported.

    return deepType.match(/^(array|bigint|date|error|function|generator|regexp|symbol)$/) ? deepType :
       (typeof obj === 'object' || typeof obj === 'function') ? 'object' : typeof obj;
  }
Enter fullscreen mode Exit fullscreen mode

Discussion (5)

Collapse
supportic profile image
Supportic • Edited on

Using a string on your function returns nan, basically this if (isNaN(+obj)) return "nan"; condition is true.

let temp = 'a string';
console.log(type(temp));
Enter fullscreen mode Exit fullscreen mode

Maybe set showFullClass variable to default true, so you don't have to provide the parameter in the function call every time.

showFullClass = showFullClass || true
Enter fullscreen mode Exit fullscreen mode

Otherwise I love the idea and I see me using this quite alot for debugging :) Good job!

Collapse
siddharthshyniben profile image
Siddharth Author • Edited on

Thanks!

Nice catch on the NaN. I think Object.is would fix it, right?

I think most people would prefer not having the full class so I set it to false

Collapse
supportic profile image
Supportic • Edited on

I think most people would prefer not having the full class so I set it to false

Oh I gotcha, I first didn't understand what you meant to do with a non provided variable but it's actually undefined when not passed and therefore false. All good.

Looks good. Very useful! For testing purposes I renamed the function to showType() but if someone wants to see the differences here you go:

const main = () => {
  const tests = [
    ['an', 'array'],
    `the result is ${1 + 2}`,
    1n,
    new Date(),
    new Error('an error'),
    function () {},
    function* () {},
    /regex/gi,
    Symbol(),
    'not a number' / 2,
    document.querySelector('body'),
    1 / 0,
    null,
  ];

  for (const [i, test] of tests.entries()) {
    console.log(test);
    console.log('showType: ' + showType(test));
    console.log('showType fullClass: ' + showType(test, true));
    console.log('typeof: ' + typeof test);

    i < tests.length-1 ? console.log('========================') : null
  }
};
Enter fullscreen mode Exit fullscreen mode
Collapse
merri profile image
Vesa Piittinen

You can also have fun with thing.__proto__.constructor.name

Collapse
siddharthshyniben profile image
Siddharth Author

That's a mouthful — So is the one in this post