DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Fixing the JavaScript typeof operator

Fixing the JavaScript typeof operator

Angus Croll user avatar by
Angus Croll
·
Aug. 09, 11 · Interview
Like (0)
Save
Tweet
Share
4.96K Views

Join the DZone community and get the full member experience.

Join For Free

working with javascript’s typeof operator is a bit like operating a clapped-out old car (or an early model dell inspiron). it gets the job done (mostly) and you learn to work around the quirks – but you probably aspire to something better.

in this article i’ll give a brief overview of typeof before introducing a tiny new function which is a fully-loaded, more reliable alternative that works directly with the language internals.


the typeof operator

how is it used?

since typeof is a unary operator, the operand follows the operator. no additional punctuation is required.

typeof 2 //"number"
typeof "belladonna" //"string"


but it works when i call it as a function?

the typeof operator is not a function. you can surround the operand with parentheses so that the expression looks like a function call, but the parentheses will simply act as a grouping operator (second only to the comma operator in the obscurity pecking order!). in fact you can decorate the operand with all manner of punctuation without derailing the operator.

typeof (2) //"number"
typeof(2) //"number"
typeof ("a", 3) //"number"
typeof (1 + 1) //"number"


what does it return?

the returned value is a somewhat arbitrary representation of the operand’s type. the table below (based on the one in the es5 spec ) provides a summary:

type of val result
undefined “undefined“
null “object“
boolean “boolean“
number “number“
string “string“
object (native and not callable) “object“
object (native or host and
callable)
“function“
object (host and not
callable)
implementation-defined


what’s wrong with typeof?

the most glaring issue is that typeof null returns “object”. it’s simply a mistake. there’s talk of fixing it in the next version of the ecmascript specification, although this would undoubtedly introduce backwards compatibility issues.

var a;
typeof a; //"undefined"
typeof b; //"undefined"
alert(a); //undefined
alert(b); //referenceerror

other than that, typeof is just not very discriminating. when typeof is applied to any object type other than function, it returns “object”. it does not distinguish between generic objects and the other built-in types (array, arguments, date, json, regexp, math, error, and the primitive wrapper objects number, boolean and string).

oh and you’ll hear folks complaining about this…

typeof nan //"number"

…but that’s not the fault of the typeof operator since the standard clearly states that nan is indeed a number.

a better way?

[[class]]

every javascript object has an internal property known as [[class]] (the es5 spec uses the double square bracket notation to represent internal properties, i.e. abstract properties used to specify the behavior of javascript engines). according to es5 , [[class]] is “a string value indicating a specification defined classification of objects”. to you and me, that means each built-in object type has a unique non-editable, standards-enforced value for its [[class]] property. this could be really useful if only we could get at the [[class]] property…

object.prototype.tostring

…and it turns out we can. take a look at the es 5 specification for object.prototype.tostring…

  1. let o be the result of calling toobject passing the this value as the argument.
  2. let class be the value of the [[class]] internal property of o .
  3. return the string value that is the result of concatenating the three strings "[object ", class , and "]".

in short, the default tostring function of object returns a string with the following format…

[object [[class]] ]

…where [[class]] is the class property of the object.

unfortunately, the specialized built-in objects mostly overwrite object.prototype.tostring with tostring methods of their own…

[1,2,3].tostring(); //"1, 2, 3"

(new date).tostring(); //"sat aug 06 2011 16:29:13 gmt-0700 (pdt)"

/a-z/.tostring(); //"/a-z/"

…fortunately we can use the call function to force the generic tostring function upon them…

object.prototype.tostring.call([1,2,3]); //"[object array]"

object.prototype.tostring.call(new date); //"[object date]"

object.prototype.tostring.call(/a-z/); //"[object regexp]"

introducing the totype function

we can take this technique, add a drop of regex, and create a tiny function – a new and improved version of the typeof operator…

var totype = function(obj) {
  return ({}).tostring.call(obj).match(/\s([a-z|a-z]+)/)[1]
}

(since an new, generic object will always use the tostring function defined by object.prototype we can safely use ({}).tostring as an abbreviation for object.prototype.tostring)

let’s try it out…

totype({a: 4}); //"object"
totype([1, 2, 3]); //"array"
(function() {console.log(totype(arguments))})(); //arguments
totype(new referenceerror); //"error"
totype(new date); //"date"
totype(/a-z/); //"regexp"
totype(math); //"math"
totype(json); //"json"
totype(new number(4)); //"number"
totype(new string("abc")); //"string"
totype(new boolean(true)); //"boolean"

..and now we’ll run the same tests with the typeof operator (and try not to gloat) …

typeof {a: 4}; //"object"
typeof [1, 2, 3]; //"object"
(function() {console.log(typeof arguments)})(); //object
typeof new referenceerror; //"object"
typeof new date; //"object"
typeof /a-z/; //"object"
typeof math; //"object"
typeof json; //"object"
typeof new number(4); //"object"
typeof new string("abc"); //"object"
typeof new boolean(true); //"object"

compare to duck-typing

duck-typing checks the characteristics of an object against a list of known attributes for a given type (walks like a duck, talks like a duck…). because of the limited usefulness of the typeof operator, duck-typing is popular in javascript. its also error-prone. for example the arguments object of a function has a length property and numerically indexed elements, but it is still not an array.

using totype is a reliable and easy alternative to duck-typing. reliable because it talks directly to the internal property of the object, which is set by the browser engine and is not editable; easy because its a three-word check.

here’s an illustrative example – a snippet which defines a non-compliant json object. the jsonparseit function accepts a function as its argument, which it can use to test the veracity of the json object before using it to parse a json string….

window.json = {parse: function() {alert("i'm not really json - fail!")}};

function jsonparseit(jsontest) {
  if (jsontest()) {
    return json.parse('{"a":2}');
  } else {
    alert("non-compliant json object detected!");
  }
}

let’s run it, first with duck-typing…

jsonparseit(function() {return json && (typeof json.parse == "function")})
//"i'm not really json - fail!"

…whoops!…and now with the totype test…

jsonparseit(function() {return totype(json) == "json"});
//"non-compliant json object detected!

could totype reliably protect against the malevolent swapping of built-in javascript objects with impostors? probably not, since the perpetrator could presumably also swap the totype function. a more secure test might call ({}).tostring directly…

function() { return ({}).tostring.call(json).indexof("json") > -1 }

..though even this would fail if object.prototype.tostring was itself maliciously re-written. still each additional defense helps.

compare to instanceof

the instanceof operator tests the prototype chain of the first operand for the presence of the prototype property of the second operand (the second operand is expected to be a constructor, and a typeerror will be thrown if it is not a function):

new date instanceof date; //true

[1,2,3] instanceof array; //true

function customtype() {};
new customtype instanceof customtype; //true

on the face of it this seems to hold promise of a nice type-checker for built-ins, however there are at least two snags with this approach:

1. several built in objects (math, json and arguments) do not have associated constructor objects – so they cannot be type-checked with the instanceof operator.

math instanceof math //typeerror

2. as @kangax and others have pointed out, a window can comprise multiple frames, which means multiple global contexts and therefore multiple constructors for each type. in such an environment, a given object type is not guaranteed to be an instanceof of a given constructor….

var iframe = document.createelement('iframe');
document.body.appendchild(iframe);

var iframearray = window.frames[1].array;
var array = new iframearray();

array instanceof array; //false
array instanceof iframearray; //true;

type-checking host objects

host objects are browser-created objects that are not specified by the es5 standard. all dom elements and global functions are host objects. es5 declines to specify a return value for typeof when applied to host objects, neither does it suggest a value for the [[class]] property of host objects. the upshot is that cross-browser type-checking of host objects is generally not reliable:

totype(window);
//"global" (chrome) "domwindow" (safari) "window" (ff/ie9) "object" (ie7/ie8)

totype(document);
//"htmldocument" (chrome/ff/safari) "document" (ie9) "object" (ie7/ie8)

totype(document.createelement('a'));
//"htmlanchorelement" (chrome/ff/safari/ie) "object" (ie7/ie8)

totype(alert);
//"function" (chrome/ff/safari/ie9) "object" (ie7/ie8)

the most dependable cross-browser test for an element might be to check for the existence of a nodetype property…

function iselement(obj) {
  return obj.nodetype;
}

…but that’s duck-typing so there are no guarantees ;-)

where should a totype function live?

for brevity, my examples define totype as a global function. extending object.prototype will get you thrown to the dragons – my preference would be to extend object directly, which mirrors the convention established by es5 (and prototype.js before that).

object.totype = function(obj) {
  if((function() {return obj && (obj !== this)}).call(obj)) {
    //fallback on 'typeof' for truthy primitive values
    return typeof obj;
  }
  return ({}).tostring.call(obj).match(/\s([a-z|a-z]+)/)[1]
}

alternatively you might choose to add the totype function to a namespace of your own, such as util.

we could get a little cleverer (inspired by chrome’s use of “global” for window.[[class]]). by wrapping the function in a global module we can identify the global object too:

object.totype = (function totype(global) {
  return function(obj) {
    if (obj === global) {
      return "global";
    }
    if((function() {return obj && (obj !== this)}).call(obj)) {
      //fallback on 'typeof' for truthy primitive values
      return typeof obj;
    }
    return ({}).tostring.call(obj).match(/\s([a-z|a-z]+)/)[1]
  }
})(this)

let’s try it out…

object.totype(window); //"global" (all browsers)
object.totype([1,2,3]); //"array" (all browsers)
object.totype(/a-z/); //"regexp" (all browsers)
object.totype(json); //"json" (all browsers)
//etc..

what totype does not do

the totype function cannot protect unknown types from throwing referenceerrors…

object.totype(fff); //referenceerror

more precisely it is the call to totype that throws the error, not the function itself. the only guard against that (as with calls to any function) is to practice good code hygiene…

window.fff && object.totype(fff);

wrap up

ok i’ve babbled on for far longer than i intended to – so congratulations if you made it to here, i hope you found it useful. i covered a lot of ground and probably made some mistakes – please feel free to let me know about them. also i’d love to hear about other people’s adventures in type-checking.

further reading

juriy zaytsev (“kangax”):
‘instanceof’ considered harmful (or how to write a robust ‘isarray’)

ecma-262 5th edition:
the typeof operator
object internal properties and methods (for more about [[class]])
object.prototype.tostring
the instanceof operator

from http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator

Operator (extension) Object (computer science) JavaScript Duck typing Property (programming) Host (Unix) Object type (object-oriented programming)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How to Submit a Post to DZone
  • The 5 Books You Absolutely Must Read as an Engineering Manager
  • How To Handle Secrets in Docker
  • HTTP vs Messaging for Microservices Communications

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: