JavaScript: Make a Real typeof

The problems with JavaScript’s typeof operator are manifold. It is so restricted, that its only use is in testing for undefined. The main culprit cause these restrictions is the loose typing together with the automatic type coercion: "2" + 2 results in 22, "2" == 2 is true, t = 2;typeof t returns "number" and t = new Number(2);typeof t returns "object" to name just a few.
The worst case is probably:

// somewhere on top of the code
var a = 2;
// some thousand lines and/or several scripts later
var b = new Number(2);
if(a === b){
    // CPR: Cardiopulmonary resuscitation
    console.log("Go on with CPR!");
} else {
    console.log("He's dead, Jim!"),
}

There are several approaches to be found in the net, but not one of them does all I want. And I want all, of course đŸ˜‰

The method to catch the case t = 2;typeof t returning "number" and t = new Number(2);typeof t returning "object" can be solved with the Object.prototype.toString method applied to the object in question.
It will return a well defined string specified in ECMAScript 5.1 in 5.2.4.2. OK, as “well defined” as a committee can go, but it is sufficient.

function xtypeof(obj){
  var  tmp = Object.prototype.toString.call(obj);
  return tmp.slice(8, -1).toLowerCase();
}

If you fear a different implementation with a different character encoding (ECMAScript’s basic is 16-bit Unicode) you can go the long way with a regular expression.

So the complete JavaScript object zoo is entirely covered by this method! Well, not entirely…one small village…sorry, couldn’t resist.
But all non-native objects are still “object”s, not what I wanted for my own prototypes.

The instanceof operator works for these, of course but you have to know them in advance or you get in trouble with the nasty undefined otherwise you could just use a list. That is not a problem, you can use a list if you use strings and use eval() to make them variables. Catch the error that obj is undefined with try/catch and call it a day.

Another method is the use of Object.prototype.constructor which returns the constructor and use a regex to get the name. That is more complicated but avoids the use of eval() and is not bound to a finite list, but has other caveats. See this post at stackoverflow for more information.
In Firefox version 35.0

function Bla() {
  this.foo = 123;
}
Bla.prototype.baz = function () {
  this.foo = 999;
}
var t = new Bla();
function Zoo() {
  this.critter = 'cheeta';
};
Bla.prototype = new Zoo();
var k = new Bla();

// console.log('xtypeof(k) = ' + xtypeof(k)); // bla
console.log('k instanceof Bla = ' + (k instanceof Bla)); // true
console.log('k instanceof Zoo = ' + (k instanceof Zoo)); // true
console.log('call k = ' + Object.prototype.toString.call(k)); // [object Object]
console.log('name k = ' + k.constructor.name); // Zoo

Interesting is the fact that k is both an instance of Bla and one of Zoo and constructor.name returns Zoo. That means we have several choices but not a single fits-all one, at least not a short one.

I have no cases of inheritance indistinguishable by instanceof in my code and only a couple of constructors, half a dozen at most if I counted them correctly. So it is either Object.prototype.toString.call together with a list (and eval() if I need it more general for testing) or the method using constructor. The constructor is a bit problematic as it seems, so it is the frist method with Object.prototype.toString.call and a list.
Here is the resulting code where I used typeof as a short, fast first test. Two problems with typeof here: the type of undefined is "undefined" and the type of null is "object". The latter gets caught by Object.prototype.toString.call (obj instanceof Null results in a ReferenceError in Firefox 35.0), the first one is no problem at all.

function xtypeof(obj) {
  'use strict';
  // try it the traditional way.
  var tmp = typeof obj;
  if (tmp !== 'object') {
    return tmp;
  } else {
    // try the toString prototype
    tmp = Object.prototype.toString.call(obj);
    // It is one of the build-ins
    if (tmp !== '[object Object]') {
      return tmp.slice(8, - 1).toLowerCase();
    } else {
      // Put your own objects here
      // The key is the lowercased name of the object, the
      // value is the correctly typed name of the object.
      // The value must be a String, hence in quotes.
      var list = {
        bigint: 'Bigint',
        bigfloat: 'Bigfloat',
        bigrational: 'Bigrational',
        complex: 'Complex',
        bla: 'Bla'
      };
      for (var p in list) {
        try {
          // Yes, kids, eval() is eeeeevil!
          // Undefined entries will cause a ReferenceError.
          tmp = eval(list[p]);
        } 
        catch (e) {
          // Nothing to catch here because the evidence
          // of non-existance is sufficient for our needs,
          // so let's...
          continue;
        }
        if (obj instanceof tmp) {
          return p;
        }
      }
      return 'object';
    }
  }
}

Despite all care taken, this is a slow function, so use it sparingly. If you know the range of objects possible in the input it is most probably more useful and definitely faster to check for them directly. E.g.:

Complex.prototype.add = function(x){
  if (!(x instanceof Complex)){
    // Assuming there exists a toComplex() method for
    // all possible input. Otherwise a TypeError gets thrown
    x = x.toComplex();
  }
};

Or the other way around

Bigint.prototype.add = function(x){
  // Bigfloat must be defined already
  // Use a try/catch construct otherwise
  if (x instanceof Bigfloat){
    return this.toBigfloat().add(x);
  }
};
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s