© 2019 Meouzer Consortium

JavaScript Data Typing isn't as Simple as you Think
Think you know how to detect a String class instance? Well think again!
Don't want to read the article? Well neither did our editors.
Just show me the code typing.js. But our editors didn't want to look at that either.

loading
Classification cats' easy but Clasifacaiton JavaScrypt element's
diff9kult cuase JavaScrypt not writan by cats. Meouzer

What about the provocative statement that the reader has no idea on how to detect a String? Well its true! You have no idea unless you have in great detail gone through some of the esoterics of JavaScript. See section Detecting Built in Class Instances.

The fact that JavaScript doesn't provide such basic functionality as String detection or ArrayBuffer detection is evidence that JavaScript has often gone down the wrong path in its youth. If JavaScript had been serious about data typing this sort of situation would never had happened.

Introduction

It's a commonly expressed thought that JavaScript is classless. This may be the reason why JavaScript doesn't provide for typing class instances. As we shall see, JavaScript had the means for full typing if the JavaScript architets had just been paying attention. That JavaScript needs robust typing like other languages should be obvious. If not, then consider the fact that it's only robust typing mechanisms that allow deep copying, serialization, and deserialization of complex JavaScript objects.

Since JavaScript lacks a robust typing system, we develop our own type() function. JavaScript has the typeof operator, but it only types primitives, functions, and the general object category. It for example doesn't type Booleans, or any other built in class. What about the instanceof operator? The problems with instanceof are enumerated.

  1. If you want to know the type of an element x, you have to test x instanceof klass against every built in class klass.
  2. instanceof is preserved under derivations.
    • if x is a Boolean then x instanceof Boolean is true. So far so good. However, y = Object.create(x) derived from x is not a Boolean yet y instanceof Boolean is also true. So instanceof can not distinguish between x, which is a Boolean, and y, which is not a Boolean.
  3. Even for mere testing, instanceof fails. As we have seen, y instanceof Boolean is not a test for y being Boolean.

In conclusion, instanceof fails miserably as a full typing mechanism.

Specification of the type() Function

The specifcation of the type() function is given below.

The type() Type Function
type(x)x
"null"A null primitive
"undefined"An undefined primitive
"boolean"A boolean primitive
"number"A number primitive
"string"A string primitive
"bigint"A bigint primitive
"symbol"A symbol primitive
"Date"A class instance of Date
"RegExp"A class instance of RegExp
"InternalError"A class instance of InternalError
"Error"A class instance of Error or proper cast to Error
"EvalError"A class instance of Error or proper cast to Error
"RangeError"A class instance of RangeError or proper cast to RangeError
"ReferenceError"A class instance of ReferenceError or proper cast to ReferenceError
"SyntaxError"A class instance of SyntaxError or proper cast to SyntaxError
"TypeError"A class instance of TypeError or proper cast to TypeError
"URIError"A class instance of URIError or proper cast to URIError
"Number"A class instance of Number
"String"A class instance of String
"Boolean"A class instance of Boolean
"Array"A class instance of Array
"Arguments"A function's argument list
"Function"A function
"Int8Array"A class instance of Int8Array
"Uint8Array"A class instance of Uint8Array
"Uint8ClampedArray"A class instance of Uint8ClampedArray
"Int16Array"A class instance of Int16Array
"Uint16Array"A class instance of Uint16Array
"Int32Array"A class instance of Int32Array
"Uint32Array"A class instance of Uint32Array
"Float32Array"A class instance of Float32Array
"Float64Array"A class instance of Float64Array
"ArrayBuffer"A class instance of ArrayBuffer
"DataView"A class instance of DataView
"Map"A class instance of Map
"Set"A class instance of Set
"WeakMap"A class instance of WeakMap
"WeakSet"A class instance of WeakSet
"Object"All elements not previously categorized in this table. The complete list of such elements follows. The items overlap.
  1. An instance of a built in class that is not a class instance
  2. A literal object
  3. A null object
  4. A function prototype
  5. A member of another data type cast to Object.prototype
  6. A class instance or modification thereof of a programmer defined class
  7. A result of calling Object.create()
  1. IE11 does not define Symbol, BigInt, and WeakSet
  2. Edge does not define BigInt
  3. InternalError is the only error class variant where the native type and method testing behaves so badly that our function typing(), discussed later, fails to type InternalErrors. We will fix this oversight only if InternalError ever becomes standard. However, userType() and dtype(), also developed later, both type InternalErrors.
  1. If x is an member of a primitive data type then type(x) is the name of that primitive data type.
  2. Otherwise, if x is a class instance of a built in class then type(x) is the name of that class.
  3. Otherwise, if x is a host object then type(x) is the native type of x
  4. Otherwise type(x) is "Object" (x must in fact be an object)
  5. type(x) respects proper casting. See Proper Casting.

The native type is discussed later.

On the Road to the Native Type

A data type is a named category of JavaScript elements. See Wikipedia: Data Type for more details.

How easy is it to determine in code the JavaScript data type of an arbitrary element x? The fact of the matter is that there is no currently known means of determination.

So we develop our own type() function, as seen in code listing 3, that returns the types of the instances of the various JavaScript data types. It is based on arcane code, which shows just how far JavaScript went off into the deep end of the theme "Let's not support basic infrastructure if we don't want to because who's going to notice anyway? Meouzer the snarky cat programming cat? Ha ha ha." However, Meouzer gets the last laugh as he gently barbecues the JavaScript committee in the most delicious way possible.

The typeof() function is good only at distinguishing between primitives, functions, and the remaining objects. The remaining infrastructure supporting typing solely resides in the instanceof operator, which is object based. That's not good enough because JavaScript data types, except for some primitives, are classes and classes have another idea for the concept of instance. It's therefore prudent to invent nomenclature to distinguish between object based instances and class based instances.

JavaScript lands the first punch since the name of the instanceof operator should be respected. If x instanceof X evaluates to true then x is an instance of the class/constructor X. What this means is that x derives from X.prototype.

We get to land the second punch and define a class instance of a class/constructor X to be an element x that is created by calling X with operator new. Exceptions are for the class/constructors Symbol and BigInt where use of new is not allowed. We define an X class instance to be a class instance of X. Every built in class Klass is a JavaScript data type because it is by definition the set of all class instances of its constructor and is therefore a named category of JavaScript objects.

For example, since Object.create(Boolean.prototype) derives from Boolean.prototype it is an instance of the Boolean class, but it's not a Boolean class instance. Now x = new Boolean(true) is a Boolean class instance. Since x is a result of the constructor call, x automatically has signed the contract that the constructor enforces and so is worthy of being called a Boolean class instance. Similarly Symbol() is a class instance of Symbol, and BigInt(7) is a BigInt class instance.

Now this is very simple but worth emphasizing. Take any built in class such as Boolean and then ask the following question. What is a Boolean? The only answer is that a Boolean is a class instance of Boolean. For example, no one would call Object.create(Boolean.prototype) a Boolean, but everyone would call new Boolean(true) a Boolean.

Before BigInt and Symbol came to be in ECMAScript-2015, you would laugh at the idea of a class constructor returning primitives. Yet now, BigInt and Symbol have decapitated their silly little heads by laughing so hard right back at you. Class instances of BigInt and Symbol are primitives. OK! The inheritance article with good reason argues that they are classes: but it could be wrong.

The general type of an object x is the name of the most derived built in class Klass for which x is an instance of Klass. Then to determine if x is a class instance of Klass one may method test the instance. That means one uses a method of Klass that throws an exception on instances that are not class instances. If x is a class instance of Klass then type(x) should be "Klass", but otherwise x is a mere instance and type(x) should be "Object". If there is no such built in class Klass for which x is an instance of, then type(x) should be "Object".

Determining the general type with instanceof is very inefficient since this involves testing x instanceof Klass against every built in class Klass. Also method testing instances is inefficient since it involves throwing and catching exceptions.

Fortunately, there is a bit of arcane code involving the native type of an object that completely eliminates the need for instanceof. It also largely eliminates the need for method testing.

For any element x, Object.prototype.toString.call(x) is a string that takes the form "[object nativeType]". So we can extract the native type as follows.

function nativeType(x) { var str = Object.prototype.toString.call(x); return str.substring(8,str.length-1); }

In ECMAScript-2009, the arcane nativeType() is a near typing of JavaScript's data types. In fact, a simple wrapper around nativeType() provides full typing.

The JavaScript committee, probably because they are a committee who didn't take minutes in their pre 2009 meetings, simply didn't realize how great the native type was, so from lack of attention to detail and not reading seven year old notes, and because they don't have cats, the committee seriously degraded the typing capabilities of nativeType() for ECMA-2015. So more work is required to obtain a typing wrapper. Worst of all, method testing is required for the following data types: ArrayBuffer, DataView, Map, Set, WeakMap, and WeakSet. These data types are called the quirk classes because the native type behaves so badly when they are involved.

We won't mention it further, but nativeType() and type() both type the host objects. For example nativeType(window) = type(window) = "Window" and nativeType(document) = type(document) = "HTMLDocument".

Coding the type() Function Yourself

First use typeof() to type the primitives. Then to type the built in classes use the following table.

The instances column refers to mere instances, which are instances of the class but not class instances of the class.

Native Types of class instances, remaining instances, and the prototype
(IE11 native type in red if different)
(Edge native type in blue if different)

GroupKlassClass instancesKlass.prototypeInstances
Group 1Date"Date" "Object""Object"
RegExp"RegExp""Object""Object"
Int8Array"Int8Array""Object""Object"
Error "Error""Object"
"Error" "Error"
"Object"
RangeError "Error" "Object"
Error "Error"
"Object"
Group 2Boolean"Boolean""Boolean" "Object"
Number"Number""Number""Object"
String"String""String""Object"
Array"Array""Array""Object"
Primitve ClassSymbol"Symbol""Symbol""Symbol"
BigInt"BigInt""BigInt""BigInt"
Quirk ClassesArrayBuffer"ArrayBuffer""ArrayBuffer""ArrayBuffer"
DataView "DataView"
"Object"
"DataView"
"Object"
"DataView"
"Object"
Set "Set"
"Object"
"Set"
"Object"
"Set"
"Object"
Map "Map"
"Object"
"Map"
"Object"
"Map"
"Object"
WeakSet"WeakSet""WeakSet""WeakSet"
WeakMap "WeakMap"
"Object"
"WeakMap"
"Object"
"WeakMap"
"Object"
Int8Array is representative of all typed arrays.
RangeError is representative of all error classes other than Error.
IE11 does not define Symbol, BigInt, and WeakSet.
Edge does not define BigInt
The JavaScript committee and Microsoft together nearly made full typing impossible by the design of the native type for these classes. Only by sheer luck does method testing work for these classes, and so we barely escape living in a world without typing and infrasctruture that results from typing such as copying and deep copying.

Believe it or not, you now have everything you need to code type(). There are three pertinent class entities to deal with: class instances, mere instances (which are instances but not class instances), and the class prototype. The type() function should map built in class instances to the name of the class, while mapping mere instances and the class prototype to "Object". Explanatory examples follow.

For the Int8Array class, nativeType() works perfectly as it yields the type across class instances, the class prototype, and mere instances: nativeType(x) = "int8Array" = type(x) if x is a class instance, but nativeType(x) = "Object" = type(x) if x is a mere instance or the prototype Int8Array.prototype.

For the Boolean class, nativeType() fails to distinguish between class instances and the prototype. If x is a Boolean class instance then nativeType(x) and nativeType(Boolean.prototype) are both "Boolean". However, type() code can simply ask if x is the prototype and if so return "Object".

For the Symbol class, all class instances x are primitives with typeof(x) being "symbol", which type(x) returns. When type() sees that the native type of some element x is "Symbol", type() knows that x is either Symbol.prototype or a mere instance of Symbol, whence "Object" is returned.

The native type significantly degrades for the Quirk classes of ECMA-2015.

For the ArrayBuffer class, nativeType(x) fails to distinguish between class instances, mere instances, and the class prototype: you always get "ArrayBuffer" for the native type. The type() function can again just ask if x is the prototype to return "Object". Then we need to distinguish between class instances of ArrayBuffer and mere instances of ArrayBuffer: this is done through method testing. x.byteLength will throw an exception if x is a mere instance, in which case type() returns "Object". Otherwise type() returns "ArrayBuffer".

The other quirk classes follow a discussion similar to that for ArrayBuffer except in IE11. In IE11 the trick that works is to use method testing as before but replacing nativeType(x) with x.__proto__.constructor.name. Another near escape!

The reader is invited to investigate what type() should do for the eight error classes.

OK! You are ready to have some real fun by coding type() yourself. If you don't have the time and just want to enjoy the show, then read on.

The Built in Type Function typeof()

The typeof() function/operator is a good start on the road to the type() function.

Data Types Recognized by the typeof Operator
Primitive Data TypeCategorizationTest x for Membership
nullThe literal null or variable set to suchx === null
undefinedThe literal undefined or variable set to such typeof(x) == "undefined"
booleanThe literals true, false or variable set to such typeof(x) == "boolean"
numberA literal number or variable set to suchtypeof(x) == "number"
stringA literal string or variable set to suchtypeof(x) == "string"
BigIntA literal BigInt or variable set to such.
Or a class instance of BigInt
typeof(x) == "bigint"
SymbolA class instance of Symboltypeof(x) == "symbol"
Object Data TypeCategorizationTest x for Membership
functionA functiontypeof(x) == "function"
objectAn objecttypeof(x) == "function" ||
typeof(x) == "object"

The typeof operator recognizes nine data type which are all inclusive: every element must be of one of these data types. They are mutually exclusive except for function and object because the latter contains the former.

There are two object data types: object, and function, which respectively refer to the set of all objects and the set of all functions Every function is in fact an object, so function is more specified than object. With these two additions, typeof is a typing function of the nine data types: it will return the most specific of the nine data types of an element.

If x is a function then typeof(x) is "function". However, if x is an object that is not a function then typeof(x) is "object".

Typing with nativeType() in ECMAScript-2009

In ECMAScript-2009, the arcane nativeType() is a near typing of JavaScript's data types. In fact, a simple wrapper otype() around nativeType() provides full typing.

Typing with otype()

The oType(x) function gives the "original type" of its argument x, which is determined at definition of x. Nothing, including atomic explosions, or change of the internal prototype of x can change the value of oType(x).

The otype() Function
function otype(x) { if (x === null) return null; if (typeof(x) != 'object' && typeof(x) != 'function') return typeof (x); var t = nativeType(x); if(t == "Arguments") return t; return (x == window[t].prototype)? "Object" : t; }

If you want otype(x) to be "Object" when x is the argument list of a function then delete the line referencing "Arguments".

The otype() Type Function for ECMAScript-2009
otype(x)x at time of definition
"null"a null
"undefined"an undefined
"boolean"a boolean
"number"a number
"string"a string
"Date"a class instance of Date
"RegExp"a class instance of RegExp
"Error"a class instance of Error
"Number"a class instance of Number
"String"a class instance of String
"Boolean"a class instance of Boolean
"Array"a class instance of Array
"Arguments"a function's argument list
"Function"a function
"Object" All elements not previously categorized in this table. The complete list of such elements follows. The items overlap.
  1. An instance of a built in class that is not a class instance
  2. A literal object
  3. A null object
  4. A function prototype
  5. A class instance or modification thereof of a programmer defined class
  6. A result of calling Object.create()

For example, otype(x) is "Error" precisely when x is originally defined as an Error class instance.

A modification of a class instance of a class X may no longer be a class instance of X.

During the reign of ECMAScript-2009, nativeType() and consequently its wrapper otype() were immutable, meaning that there was nothing one could do to change the value of nativeType(x) or otype(x) for any element x.

Typing with ctype()

We want to modify otype() so that it respects proper casting: see Proper Casting. If x is a built in class instance then the only proper cast of x is x.__proto__ = Object.prototype. This cast is easy to detect and so ctype() is a barely visible modification of otype().

The ctype() Function
function ctype(x) { if (x === null) return "null"; if (typeof (x) != 'object' && typeof (x) != 'function') return typeof (x); const t = nativeType(x); if(t == "Arguments") return t; return (x == window[t].prototype || x.__proto__ == Object.prototype)? "Object" : t; }

If you want ctype(x) to be "Object" when x is the argument list of a function then delete the line referencing "Arguments".

The ctype() Type Function for ECMAScript-2009
ctype(x)x
"null"a null
"undefined"an undefined
"boolean"a boolean
"number"a number
"string"a string
"Date"a class instance of Date
"RegExp"a class instance of RegExp
"Error"a class instance of Error
"Number"a class instance of Number
"String"a class instance of String
"Boolean"a class instance of Boolean
"Array"a class instance of Array
"Arguments"a function's argument list
"Function"a function
"Object"All elements not previously categorized in this table. The complete list of such elements follows.
  1. An instance of a built in class that is not a class instance
  2. A literal object
  3. A null object
  4. A function prototype
  5. A member of another data type cast to Object.prototype
  6. A class instance or modification thereof of a programmer defined class
  7. A result of calling Object.create()

As noted, ctype() respects (proper) casting. For example, if x = new Boolean(true) and x is cast to Object, then ctype(x) is "Object". If instead x were cast to Number then ctype(x) does not become "Number" nor is it required to do so since the cast is improper. No programmer ever has the rational to make an improper cast because doing so always results in a broken instance. ctype() has every right to do whatever it wants with improper casting. What ctype() actually does is to simply ignore improper casting. So the ctype of a Boolean cast to Number remains "Boolean".

Warning: otype() and ctype() are created for ECMA-2009. You can still use them today but must be aware that both will return erroneous information, in a browser specific way, for instances and prototypes of the ECMA-2015 quirk classes.

For example even today, the test ctype(x) == "String" is a valid detection test for Strings, and the test ctype(x) == "Int8Array" is a valid detection test for Int8Arrays even though Int8Array is a ECMA-2015 class.

However, the test test ctype(x) == "Set" is not a valid detection set for Sets, because Set is a quirks class. For instance, in Firefox if x is Object.create(Object.create(Set.prototype)) then ctype(x) is "Set" even though x is not a class instance of Set.

Detecting Built in Class Instances

Impress your friends. Ask them how to test if an element x is a String. They will surely tell you to use the instanceof operator. You can then amaze them by telling them that they are dead wrong. You can then doubly amaze them by telling them that they will never find a correct test.

Look at the isKlass() functions in typing.js and in particular look at the isString() function for the very best test for detecting a String class instance.

By now the reader knows that a String and a class instance of String are synonymous. Also by now the reader knows the expression x instanceof String is useless because if it evaluates to true all you know is that x derives from String.prototype. For example x instanceof String evaluates to true if x is Object.create(Object.create(String.prototype)), which certainly isn't a String. Here x is two degrees from String.prototype.

Closely examining the isKlass() functions, the Native Types table, and the QuirksInstance object which provides method testing for detection of class instances of the quirks classes, should help prepare you for the next section.

A Direct but Grungy Way to Code the type() Function

ECMA-2015 adds quite a number of new built in classes. If the native type was handled as it was in ECMA-2009, then the ctype() function or a mild modification of it would work out of the box to provide full typing. However, instead the typing capabilities of the native type were so seriously degraded we end up with a somewhat complicated typing function, even though it is short.

The type_grungy() Function
function type_grungy(x) { if (x === null) return "null"; if (typeof(x) != 'object' && typeof(x) != 'function') return typeof(x); const t = nativeType(x); if(t == "Arguments") return t; if(x == window[t].prototype || x.__proto__ == Object.prototype || x.__proto__ == undefined || t == "BigInt" || t == "Symbol") return "Object"; if (t == "Error") return (x == x.constructor.prototype)? "Object" : x.__proto__.constructor.name; if(QuirksInstance[t]) return QuirksInstance[t](x)? t : "Object"; // For IE11 const ct = x.__proto__.constructor.name; if(QuirksInstance[ct]) return QuirksInstance[ct](x)? ct : "Object"; return t; } const QuirksInstance = { ArrayBuffer:function (x) { try { ArrayBuffer.prototype.slice.call(x,0,0)} catch (e) { return false; } // x is a mere instance of ArrayBuffer return true; // x is a class instance of ArrayBuffer }, DataView: function (x) { try { DataView.prototype.getUint8.call(x,0)} catch (e) { return false;} return true; }, Map: function (x) { try { Map.prototype.has.call(x,{})} catch (e) { return false; } return true; }, Set:function (x) { try {Set.prototype.has.call(x,{});} catch (e) { return false;} return true; }, WeakMap:function(x) { try { WeakMap.prototype.has.call(x,"key"); } catch (e) { return false; } return true; }, WeakSet:function(x){ try {WeakSet.prototype.has.call(x,"value");} catch (e) { return false;} return true; } }

Explanation of type_grungy() Code

Lines 2 and 3 return the recognized types of the primitives. It is a mistake by JavaScript that typeof(null) is "object" because null is a primitive, not an object. Thus we have to set the type of null to "null" by hand. Also of particular note, if x is a class instance of Symbol or BigInt then x is a primitive and "symbol" and "bigint" are respectively returned.

Starting on line 4 we know x is an object. If JavaScript had not engaged is so much ad-hoc randomness, we would just be able to return the native type.

On line 5, "Arguments" is returned if x is the argument list of a function. If instead you prefer "Object" to be returned then delete line 5.

Lines 6-8 checks for special cases where "Object" should be returned. The native type often does not distinguish between a class instance and the prototype: so if x is the prototype, "Object" is returned. Next if x has been cast to Object.prototype the cast is respected by returning "Object". If the __proto__ attribute is undefined then x is a null object and "Object" is returned. Now if the native type is "BigInt" or "Symbol" then x is an instance of BigInt or Symbol but not a class instance, whence "Object" is returned.

For lines 9 and 10, if the native type is "Error" then necessarily x is a class instance or the prototype of one of the following classes: Error, EvalError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError, or URIError. If x is a prototype return "Object". Otherwise return the name of the class as determined by the name of the constructor.

Consider line 11 outside IE11. If QuirksInstance[t] is defined then the nativeType t corresponds to a quirk class, say Set. For Set, class instances, mere instances, and the prototype all have "Set" as the native type. The possibility that x is Set.prototype was weeded out on line 6. The method test QuirksInstance["Set"](x) returns true if x is a class instance of Set but false is x is a mere instance of Set. Thus line 11 returns "Set" or "Object" depending on whether x is a class instance or a mere instance. This discussion applies to the other quirk classes including ArrayBuffer.

Consider IE11. All possibilities for the quirks class ArrayBuffer have been taken care of on lines 6 and 11 as the nativeType works with ArrayBuffer like it does with the other browsers. Let's take a look at the quirk class Set. Class instances of Set, mere instances of Set, and Set.prototype all have native type "Object". If x is Set.prototype then code falls through line 13 so that "Object" is returned on line 14. If x is a Set class instance then ct is "Set", and on line 13 x passes the method test so ct = "Set" is returned. If x is a mere Set instance then either method testing fails on line 13 and "Object" is returned or code falls through line 13 and "Object" is returned on line 14.

Oh by the way! Line 11 can be deleted. Can you figure out why?

The Official Way to Code the type() Function

By direct coding of the type() function we mean that no prototyping is involved. For other direct ways of coding type() see Other Ways to Code type(). Direct ways are necessarily complicated because the native type behaves so badly in EMCA-2015. Do this and do that, test this and test that, and yada yada yada it's a Boolean. Typing built in class instances is too slow. By prototyping, most of the complexities go away, while Booleans, ArrayBuffers and the other built in class instances are quickly recognized as are the other objects derived from the built in class prototypes. Host objects are also quickly typed. So the whole world is quickly typed.

We for example don't create a Boolean.prototype.type() function because the name type could easily conflict with other libraries. Instead we prototype a Symbol. The symbol is typeSymbol = Symbol("type") and Boolean.prototype[typeSymbol]() is the function that types all objects derived from Boolean.prototype. Do this for all the other built in classes. Then the type() function will wrap all the prototyped versions.

The type() function is then especially simple.

The type() Function
(from typing.js)
function type(x) { if (x === null) return "null"; if (typeof(x) !== 'object' && typeof(x) !== 'function') return typeof (x); if(x.__proto__ === undefined) return "Object"; // handle null objects return x[typeSymbol](); // handle standard objects }

Let's now see how prototyping typeSymbol goes with some examples.

Simply refer to the native type table for Booleans to get the following code.

Object.defineProperty(Boolean.prototype, typeSymbol, { value:function() { if(this === Boolean.prototype) return "Object"; return nativeType(this); } });

nativeType() types correctly for mere instances and class instances of the Boolean class. However, nativeType() incorrectly types Boolean.prototype.

Object.defineProperty(RegExp.prototype, typeSymbol, { value:function() { return nativeType(this); } });

nativeType() correctly types RegExp.prototype, class instances of RegExp and mere instances of RegExp.

Object.defineProperty(URIError.prototype, typeSymbol, { value:function() { if(this === URIError.prototype) return "Object"; return nativeType(this) === "Error"? "URIError":"Object"; } });

nativeType() incorrectly types URIError.prototype. An instance of URIError is a class instance of URIError precisely when the native type is "Error".

Object.defineProperty(BigInt.prototype, typeSymbol, { value:function() { if(typeof(this) === "bigint") return "bigint"; return "Object"; } });

Return "bigint" precisely when a primitive. Otherwise, its an object and "Object" is returned.

Object.defineProperty(Map.prototype, typeSymbol, { value:function() { if(this === Map.prototype) return "Object"; try {Map.prototype.has.call(this,{}) } catch (e) { return "Object"; } return "Map"; } });

The native type incorrectly types Map.prototype. The native type also incorrectly types class instances and mere instances. So we method test. An exception is thrown precisely when a mere instance.

Object.defineProperty(Object.prototype, typeSymbol, { value:function() { return nativeType(this); } });

The default implementation of typeSymbol. It does the right thing for host objects, and the remaining standard objects that don't derive from a built in class prototype.

The userType() Function: Another way to code type()

Here the user is the programmer.

The userType() function is basically equivalent to the type() function. Here's the comparison. The call userType(x) will insure proper user/programmer defined data types are returned if found. Otherwise the calls userType(x) and type(x) return the same value.

A proper class/constructor is a class Klass for which Klass.prototype.constructor is Klass. A proper prototype is a prototype P for which P = P.constructor.prototype.

User defined derived classes tend not to be proper because the programmer usually doesn't bother to correctly set the constructor.

If the programmer doesn't bother to make some user class Klass proper by setting Klass.prototype.constructor = Klass, then some code in our library may not work as the programmer expects. Basically, improper classes are ignored.

In the next article we show how to decorate the userType() function to give more information suitable for use in stringify(x) which dumps the object x.

The userType() Function
function userType(x) { const t = type(x); if(t !== "Object") return t; try { x.constructor; } catch(e) { return "CrossOriginObject";} const P = getClassPrototype(x).classPrototype; if(P === null) return "Object"; // x is null object if(P === x) return "Object"; // x is function prototype if(Object.getPrototypeOf(x) !== P ) return "Object"; return isNativeCode(P.constructor)? "Object": P.constructor.name; } function getClassPrototype(x) { // Returns most derived proper class prototype from which x derives, // and the number of degrees to it. var P = x; var n = 0; while(!isClassPrototype(P) && P != null) { n++; P = Object.getPrototypeOf(P); } return {classPrototype:P, degree:n}; }
Examples
\numbersRestart function foo(){} function bar(){} function foobar(){}; // Derive bar from foo but don't set the constructor. bar.prototype = Object.create(foo.prototype); // Derive foobar from foo and correctly set the construtor to foobar. foobar.prototype = Object.create(foo.prototype, { constructor: { value:foobar } // other properties of foobar.prototype here. }); // All logs, log true. const x = new foobar(); console.log(type(x) == "Object"); console.log(userType(x) == "foobar"); // foobar is a proper class. const y = new bar(); console.log(type(y) == "Object"); console.log(userType(y) == "Object"); // "Object" not "bar" because   //bar is not a proper class. // Now here's a problem with user defined classes // z is one degree away from foobar.prototype. const z = Object.create(foobar.prototype); console.log(userType(z) == "foobar"); // incorrectly reports that   // z is a foobar class instance.

The problem with erroneous foobar class instance detection is not quite as bad as it sounds. For example let x = new foobar(...) and y = Object.create(foobar.prototype). Both x and y are instances of foobar one degree from foobar.prototype. However, there is no test that can determine that x is a foobar class instance and that y is not a foobar class instance. So both are reported as foobar class instances with a reporting error for y.

Now chances are that y will come from the class derivation process as in y = foobar.prototype = Object.create(foo.prototype) and where foobar.prototype.constructor is set to foobar. Then type(y) is "Object" and there is no reporting error.

Also y might be turned into an actual foobar class instance via foobar.call(y,...) in which case there is no reporting error for y.

If y = Object.create(foobar.prototype) is just left dangling, there may not be much use for y so with common programming practices, chances are there will be no reporting error because y wouldn't be created in such a way in the first place.

In summary, if foobar() is a proper programmer defined class then userType(z) returns "foobar" precisely when z is an instance of foobar one degree away from foobar.prototype, and z itself is not a proper prototype.

Is an Element a Host Object?

The host objects are window, document and the HTML elements, which are links, metas, scripts, styles, divs, ..., as seen by JavaScript. For example, as seen by JavaScript, a div is a class instance of HTMLDiv.

function isHostObject(x) { return nativeType(x) == "Window" || nativeType(x).substring(0,4) == "HTML"; }

Now here's a warning that probably isn't necessary. We are not interested in ever examining the prototypes, derivations, or properties, of the top level host objects, and it's assumed the programmer will never apply isHostObject() to such objects.

The main problem with typing such objects mostly resides with IE11 where constructors of host objects are not functions. There are other browser inconsistencies that make typing such exotic objects problematical.

The isKlass() Functions

Be sure to look at the isKlass() functions at the end of typing.js. For any specified built in class Klass, an element x is a class instance of Klass precisely when isKlass(x) evaluates to true. We just give a few examples.

Just a few Examples of the isKlass() Functions from typing.js
function isDate(x) // Is x a class instance of Date? { return nativeType(x) == "Date"; } function isNumber(x) // Is x a class instance of Number? { return typeof(x) == "object" && nativeType(x) == "Number" && x != Number.prototype; } function isRangeError(x) // Is x a class instance of RangeError? { return nativeType(x) == "Error" && x.__proto__.constructor.name == "RangeError"; } function isArrayBuffer(x) // Is x a class instance of ArrayBuffer? { return isObject(x) && Object.getPrototypeOf(x) === ArrayBuffer.prototype && QuirksInstance["ArrayBuffer"](x); } function isBigInt(x) // Is x a class instance of BigInt? { return typeof(x) == "bigint"; } // Written only for contrast with isBigInt(). function isMereBigInt(x) // Is x a mere instance of BigInt? { return nativeType(x) == "BigInt" && x != BigInt.prototype; }

Appendices

Proper Casting

Our type() function respects proper casting of one class to another. Examples of proper casting follow.

  1. Consider a built in class Klass instance x.
    • The upwards cast x.__proto__ = Object.prototype is proper and type(x) should change from "Klass" to "Object".
    • If x is then downwards cast back to Klass via x.__proto__ = Klass.prototype, then type(x) should change back to "Klass".
  2. Consider a specialized Error class such as RangeError.
    • If x is a class instance of RangeError then the upwards cast x.__proto__ = Error.prototype should change type(x) from "RangeError" to "Error"
    • If x is a class instance of Error then the downwards cast x.__proto__ = RangeError.prototype should change type(x) from "Error" to "RangeError".
      • In the browsers RangeError inherits all its properties from Error, whence the downward cast makes sense. However, if they ever meet the specs and give RangeError its own message property, the downwards cast is problematic and so wouldn't be considered proper. However, everything is good. If the cast is proper then type() respects it. If the cast is improper type() can do anything it wants, in this case respecting it.

The above two points list all possible proper casts between built in classes. Any other cast between classes must be sidewise, and therefore improper since it results in a broken object. For example if the Boolean class instance x = new Boolean(true) is cast to Number via x.__proto__ = Number.prototype then x is a broken Number.

No programmer ever has the rational to make an improper cast because doing so always results in a broken instance. Therefore the type() function can return whatever it wants with an object that has been improperly cast.

Prior to ECMAScript-2015, the Native Type was so Nice

Let's go back in time to ECMAScript-2009, the version previous to ECMAScript-2015. The native type then was a great useful concept.

In the general version of EMCA, the only way to change the native type of an element is to cast the element. So immutability of the native type is equivalent to invariance of the native type with respect to casting.

It is very clear that prior to ECMAScript-2015, immutability of the native type was an iron clad principle made by intentional design. Either that, or the JavaScript committee enforced, in a comprehensive and consistent way across the full gamut of possibilities, something completely by dumb luck.

Immutability of the native type allows a typing function to detect improper casts and throw exceptions on such. In ECMAScript-2015, the native type is no longer immutable.

The great thing about ECMAScript-2009 was that the native type distinguished between class instances of Klass and mere instances of Klass where Klass is a built in class. The native type of the former is "Klass" while the native type of the latter is "Object". This is no longer true in ECMAScript-2015, where the native type behaves in an random ad-hoc manner.

    1. If nativeType(x) is "Undefined", x was originally and still must be an undefined.
    2. If nativeType(x) is "Null", x was originally and still must be a null.
    3. If nativeType(x) is "Date", x was originally a Date.
    4. If nativeType(x) is "RegExp", x was originally a RegExp.
    5. If nativeType(x) is "Error", x was originally an Error.
    1. If nativeType(x) is "Number", then originally x was a number, a Number, or Number.prototype.
    2. If nativeType(x) is "Boolean", then originally x was a boolean, a Boolean, or Boolean.prototype.
    3. If nativeType(x) is "String", then originally x was a string, a String, or String.prototype.
    4. If nativeType(x) is "Array", then originally x was an Array, or Array.prototype.
  1. If nativeType(x) is "Arguments", then originally x was the argument list of a function.
  2. If nativeType(x) is "Function", then originally x was Object, Function, Function.prototype, a built in function, or a programmer defined function.
  3. If nativeType(x) is "Object", then originally x was Object.prototype, Date.prototype, RegExp.prototype, Error.prototype, a literal object, a null object, a prototype or instance of a programmer defined function, or an object created using Object.create().

Object, Object.prototype, and objects

Let's set straight some common misconceptions about objects. By definition an object is any element that is not a primitive. Small object is not a concept that derives from large Object in any way: These two concepts are very independent.

There is no Object class. The Object function, even though used with operator new, is not a constructor and in fact is both wacky and nearly useless.

Object certainly violates the internal prototype rule required of constructors, which says that when called with new the constructor must set the internal prototype of the return object to the functions prototype. It also often copies its argument rather than return a newly created object. Thus there is no class associated with the Object function: That's right, there is no Object class! Those who think/say otherwise ignore well established semantics on what it means to be a class in JavaScript.

The best one can do is to think of Object as a broken/quasi/pseudo/etc class. The only thing truly useful about Object is its prototype from which so many objects derive, and therefore inherit the many properties/methods of Object.prototype. JavaScript could easily have hidden or eliminated Object and exposed only its prototype. To maintain patterns involving class inheritance, JavaScript wanted Object.prototype to in fact be a prototype and so had to invent the Object function as a placeholder for an actual constructor.

The instanceof operator

So what does x instanceof Object mean? It means the same thing as if Object were any other function used as a constructor or not. It means that x derives from Object.prototype and nothing more. Even with a direct derivation where x.__proto__ is Object.prototype, x can not be a member of the non existing Object class.

There are objects that do not derive from Object.prototype: for example a null object such as Object.create(null). The set of all objects is partitioned into the standard objects and the null objects. The standard objects are Object.protoype and all objects deriving from it. The null objects are the objects neither being Object.prototype nor deriving from it.

It follows that x instanceof Object is not a test for x being an object. It fails for null objects x, and it fails for x = Object.prototype the most important object of all. However, it is a valid test for x being a standard object not equal to Object.prototype.

Is there an Arguments Class?

Meouzer says no, well maybe, and there might be one, but there isn't.

var arguments = (function(){ return arguments; })(); alert(nativeType(arguments)); // Arguments var Arguments = arguments.__proto__; alert(Arguments == Object.prototype);

Line 3 says there might be an Arguments class. As a proto class it would be given by Arguments in line 4. However, line 5 says as proto classes any Arguments class is the same as Object.prototype. So there's not much new under the sun.

Since arguments derives directly from Object.prototype, it behaves similarly to a literal object with some dressing on it.

Other Ways to Code type()

The Simplest Direct Way to Code type()
function type(x) { if (x === null) { return "null"; } else if (typeof (x) != 'object' && typeof (x) != 'function') { return typeof (x); } var nt = nativeType(x); if(nt === "BigInt" || nt === "Symbol") { return "Object"; } else if (nt == "Arguments") { return "Arguments"; } else if (nt == "Error") { return x.__proto__.constructor.name; } else if (window[nt] && (x != window[nt].prototype)) { if(QuirksInstance[nt]) { if (QuirksInstance[nt](x)) { return nt; } else { return "Object"; } } else { return nt; } } else { return "Object"; } }
A Direct Way to Code the userType() Function
function userType(x) { if (x === null) return "null"; if (typeof (x) != 'object' && typeof (x) != 'function') return typeof (x); try { x.constructor; } catch(e) { return "CrossOriginObject";} // Needed because for IE11, host constructors are not functions // and so getClassPrototype(P) must not be allowed to execute. if(isHostObject(x)) return nativeType(x); if(nativeType(x) == "Arguments") return "Arguments"; // Get most derived proper class prototype from which x derives. const P = getClassPrototype(x); if(P == null) return "Object"; // x is null object if(P == x) return "Object"; // x is function prototype // x is at least two degrees from prototype if((Object.getPrototypeOf(x) != P) ) return "Object"; if(isNativeCode(P.constructor)) { const name = P.constructor.name; if(name == "BigInt" || name == "Symbol") return "Object"; if(QuirksInstance[name]) return QuirksInstance[name](x)? name: "Object"; if(nativeType(x) == "Error"){return x.__proto__.constructor.name} return nativeType(x); } // x is a class instance of a programmer defined function // If "Object" is returned instead then userType() is equivalent to type() return P.constructor.name; }