DEV Community

Siddharth
Siddharth

Posted on

5 1

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

Top comments (5)

Collapse
 
supportic profile image
Supportic • Edited

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 • Edited

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

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
 
siddharthshyniben profile image
Siddharth

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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay