Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

_.toType for robust type checking, improvement of typeof #269

Closed
wants to merge 2 commits into from

4 participants

@josscrowcroft

Adds _.toType() method, borrowed from Angus Croll's article on "Fixing the JavaScript typeof operator" (http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/). Includes some tests, too.

Normally I only ever use typeof whatever !== "undefined" since as Angus points out, typeof is pretty useless for many other cases, for example:

typeof null; // "object"
typeof {a: 4}; // "object"
typeof [1, 2, 3]; // "object"
(function() { return 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"

vs:

_.toType({a: 4}); // "Object"
_.toType([1, 2, 3]); // "Array"
(function() { return _.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"

NB. It might make more sense to return types as lowercase in some scenarios (for example "Undefined" or "Null", or where the difference between "string" and "String" might cause issues) - not sure really. Perhaps the function could return lowercase by default, and add an option eg. preserveCase = preserveCase || false

@OnesimusUnbound

Add a test case for Function type, something like ok(_.toType(function(){}) === "Function", 'function(){} is a Function');. Anyway, I've test it and it's working.

@josscrowcroft

Re: above commit, adds two more tests for Function types.

@michaelficarra
Collaborator

Can you produce a use case for this? I'm failing to see how this is useful considering underscore already has the various _.isX methods. You say this "fixes" typeof, but typeof only has two valid uses, one of which can't be emulated (testing if a variable has been declared without throwing a ReferenceError) and another (existence of [[Call]]) which is already covered by _.isFunction ... mostly (at least as well as this would cover it).

edit: Oh, I see. I should have looked at the code. You're just pulling the [[Class]]. And what practical use does that have?

@jashkenas
Owner

Yep -- I'm afraid this method goes almost entirely counter to the spirit of Underscore: Looking for explicit types is a fool's game in JavaScript, because they're very weak (new Number vs. 5), and good for almost nothing.

If you want to dispatch differently based on the type being passed as an argument to a function ... just check for that type: which the _.is[Type] family of functions is already able to do far more efficiently than a toString test.

@jashkenas jashkenas closed this
@josscrowcroft

Fair play, cheers for the feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 34 additions and 0 deletions.
  1. +23 −0 test/objects.js
  2. +11 −0 underscore.js
View
23 test/objects.js
@@ -238,6 +238,29 @@ $(document).ready(function() {
ok(_.isUndefined(iUndefined), 'even from another frame');
});
+ test("objects: toType", function() {
+ ok(_.toType(1) === "number", '1 is a "number"');
+ ok(_.toType(-Infinity) === "number", '-Infinity is a "number"');
+ ok(_.toType(new Number) === "Number", '`new Number` is a "Number"');
+ ok(_.toType(NaN) === "Number", 'NaN is a "Number" (for realz)');
+ ok(_.toType(function(){}) === "Function", '`function(){}` is a "Function"');
+ ok(_.toType(window.alert) === "Function", '`window.alert` is a "Function"');
+ ok(_.toType(null) === "Null", 'null is type "Null"');
+ ok(_.toType(undefined) === "Undefined", 'while we\'re at it, undefined is "Undefined"');
+ ok(_.toType("o hai") === "string", '"o hai" is a "string"');
+ ok(_.toType(new String("o hai")) === "String", 'but `new String("o hai") is a "String"`');
+ ok(_.toType(false) === "Boolean", 'false is "Boolean"');
+ ok(_.toType(new Boolean(true)) === "Boolean", '`new Boolean(true)` is also "Boolean"');
+ ok(_.toType(/a-z/) === "RegExp", '/a-z/ is a "RegExp"');
+ ok(_.toType({a: 4}) === "Object", '`{a:4}` is type "Object"');
+ ok(_.toType([1, 2, 3]) === "Array", '`[1,2,3]` is type "Array"');
+ ok((function() {return _.toType(arguments)})() == "Arguments", 'even the arguments object gets a type of its own, "Arguments"');
+ ok(_.toType(new ReferenceError) === "Error", '`new ReferenceError` is type "Error"');
+ ok(_.toType(new Date) === "Date", 'new Date is a "Date"');
+ ok(_.toType(Math) === "Math", 'Math is type "Math"');
+ ok(_.toType(JSON) === "JSON", 'JSON is, of course, "JSON" type');
+ });
+
if (window.ActiveXObject) {
test("objects: IE host objects", function() {
var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
View
11 underscore.js
@@ -719,6 +719,17 @@
return obj === void 0;
};
+ // Robust type detection, uses a regex to match the return from native object.toString()
+ // Falls back to native 'typeof' for truthy primitive values (credit: Angus Croll)
+ // http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
+ _.toType = function(obj) {
+ if((function() {return obj && (obj !== this)}).call(obj)) {
+ return typeof obj;
+ }
+ return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1];
+ };
+
+
// Utility Functions
// -----------------
Something went wrong with that request. Please try again.