[This post is a copy of my Adobe Developer Connection article, I’m publishing it here for archival purposes.]
Primitives. The following values are primitive:
> var str = "abc"; > str.foo = 123; // try to add property "foo" 123 > str.foo // no change undefinedAnd primitives are compared by value, they are considered equal if they have the same content:
> "abc" === "abc" true
Objects. All non-primitive values are objects. Objects are mutable:
> var obj = {}; > obj.foo = 123; // try to add property "foo" 123 > obj.foo // property "foo" has been added 123And objects are compared by reference. Each object has its own identity and two objects are only considered equal if they are, in fact, the same object:
> {} === {} false > var obj = {}; > obj === obj trueWrapper object types. The primitive types boolean, number and string have the corresponding wrapper object types Boolean, Number and String. Instances of the latter are objects and different from the primitives that they are wrapping:
> typeof new String("abc") 'object' > typeof "abc" 'string' > new String("abc") === "abc" falseWrapper object types are rarely used directly, but their prototype objects define the methods of primitives. For example, String.prototype is the prototype object of the wrapper type String. All of its methods are also available for strings. Take the wrapper method String.prototype.indexOf. Primitive strings have the same method. Not a different method with the same name, literally the same method:
> String.prototype.indexOf === "".indexOf true
> Object.getPrototypeOf({}) === Object.prototype true
> Object.create(Object.prototype) {}Object.create() can do more, but that is beyond the scope of this post.
> Object.prototype.isPrototypeOf({}) true
> function Foo() { } > Foo.prototype.constructor === Foo true > RegExp.prototype.constructor === RegExp trueAll instances of a constructor inherit that property from the prototype object. Thus, we can use it to determine which constructor created an instance:
> new Foo().constructor [Function: Foo] > /abc/.constructor [Function: RegExp]
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"The only way to access it from JavaScript code is via the default toString() method, Object.prototype.toString(). That method is generic and returns
> Object.prototype.toString.call(undefined) '[object Undefined]' > Object.prototype.toString.call(Math) '[object Math]' > Object.prototype.toString.call({}) '[object Object]'Therefore, the following function returns the [[Class]] of a value x:
function getClass(x) { var str = Object.prototype.toString.call(x); return /^\[object (.*)\]$/.exec(str)[1]; }Here is that function in action:
> getClass(null) 'Null' > getClass({}) 'Object' > getClass([]) 'Array' > getClass(JSON) 'JSON' > (function () { return getClass(arguments) }()) 'Arguments' > function Foo() {} > getClass(new Foo()) 'Object'
typeof valuereturns one of the following strings, depending on the operand value:
Operand | Result |
undefined | "undefined" |
null | "object" |
Boolean value | "boolean" |
Number value | "number" |
String value | "string" |
Function | "function" |
All other values | "object" |
typeof returning "object" for null is a bug. It can’t be fixed, because that would break existing code. Note that a function is also an object, but typeof makes a distinction. Arrays, on the other hand, are considered objects by it.
value instanceof TypeThe operator looks at Type.prototype and checks whether it is in the prototype chain of value. That is, if we were to implement instanceof ourselves, it would look like this (minus some error checks, such as for type being null):
function myInstanceof(value, Type) { return Type.prototype.isPrototypeOf(value); }instanceof always returns false for primitive values:
> "" instanceof String false > "" instanceof Object false
<html> <head> <script> // test() is called from the iframe function test(arr) { var iframeWin = frames[0]; console.log(arr instanceof Array); // false console.log(arr instanceof iframeWin.Array); // true console.log(Array.isArray(arr)); // true } </script> </head> <body> <iframe></iframe> <script> // Fill the iframe var iframeWin = frames[0]; iframeWin.document.write( '<script>window.parent.test([])</'+'script>'); </script> </body> </html>Therefore, ECMAScript 5 introduced Array.isArray() which uses [[Class]] to determine whether a value is an array. The intention was to make JSON.stringify() safe. But the same problem exists for all types when used in conjunction with instanceof.
> Object.prototype {} > Object.keys(Object.prototype) []Unexpected. Object.prototype is an object, but it is not an instance of Object. On one hand, both typeof and [[Class]] recognize it as an object:
> getClass(Object.prototype) 'Object' > typeof Object.prototype 'object'On the other hand, instanceof does not consider it an instance of Object:
> Object.prototype instanceof Object falseIn order for the above result to be true, Object.prototype would have to be in its own prototype chain. But that would cause a cycle in the chain, which is why Object.prototype does not have a prototype. It is the only built-in object that doesn’t have one.
> Object.getPrototypeOf(Object.prototype) nullThis kind of paradox holds for all built-in prototype objects: They are considered instances of their type by all mechanisms except instanceof.
Expected. [[Class]], typeof and instanceof agree on most other objects:
> getClass({}) 'Object' > typeof {} 'object' > {} instanceof Object true
> Function.prototype("a", "b", 1, 2) undefinedUnexpected. Function.prototype is a function, but not an instance of Function: On one hand, typeof, which checks whether an internal [[Call]] method is present, says that Function.prototype is a function:
> typeof Function.prototype 'function'The [[Class]] property says the same:
> getClass(Function.prototype) 'Function'On the other hand, instanceof says that Function.prototype is not an instance of Function.
> Function.prototype instanceof Function falseThat’s because it doesn’t have Function.prototype in its prototype chain. Instead, its prototype is Object.prototype:
> Object.getPrototypeOf(Function.prototype) === Object.prototype trueExpected. With other functions, there are no surprises:
> typeof function () {} 'function' > getClass(function () {}) 'Function' > function () {} instanceof Function trueFunction is also a function in every sense:
> typeof Function 'function' > getClass(Function) 'Function' > Function instanceof Function true
> Array.prototype [] > Array.prototype.length 0[[Class]] also considers it an array:
> getClass(Array.prototype) 'Array'So does Array.isArray(), which is based on [[Class]]:
> Array.isArray(Array.prototype) trueNaturally, instanceof doesn’t:
> Array.prototype instanceof Array falseWe won’t mention prototype objects not being instances of their type for the remainder of this section.
> RegExp.prototype.test("abc") true > RegExp.prototype.test("") trueRegExp.prototype is also accepted by String.prototype.match, which checks whether its argument is a regular expression via [[Class]]. And that check is positive for both regular expressions and the prototype object:
> getClass(/abc/) 'RegExp' > getClass(RegExp.prototype) 'RegExp'Excursion: the empty regular expression. RegExp.prototype is equivalent to the “empty regular expression”. That expression is created in either one of two ways:
new RegExp("") // constructor /(?:)/ // literalYou should only use the RegExp constructor if you are dynamically assembling a regular expression. Alas, expressing the empty regular expression via a literal is complicated by the fact that you can’t use //, which would start a comment. The empty non-capturing group (?:) behaves the same as the empty regular expression: It matches everything and does not create captures in a match.
> new RegExp("").exec("abc") [ '', index: 0, input: 'abc' ] > /(?:)/.exec("abc") [ '', index: 0, input: 'abc' ]Compare: an empty group not only holds the complete match at index 0, but also the capture of that (first) group at index 1:
> /()/.exec("abc") [ '', // index 0 '', // index 1 index: 0, input: 'abc' ]Interestingly, both an empty regular expression created via the constructor and RegExp.prototype are displayed as the empty literal:
> new RegExp("") /(?:)/ > RegExp.prototype /(?:)/
> getClass(new Date()) 'Date' > getClass(Date.prototype) 'Date'Dates wrap numbers. Quoting the ECMAScript 5.1 specification:
A Date object contains a Number indicating a particular instant in time to within a millisecond. Such a Number is called a time value. A time value may also be NaN, indicating that the Date object does not represent a specific instant of time.Two common ways of accessing the time value is by calling valueOf or by coercing a date to number:Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.
> var d = new Date(); // now > d.valueOf() 1347035199049 > Number(d) 1347035199049The time value of Date.prototype is NaN:
> Date.prototype.valueOf() NaN > Number(Date.prototype) NaNDate.prototype is displayed as an invalid date, the same as dates that have been created via NaN:
> Date.prototype Invalid Date > new Date(NaN) Invalid Date
> Number.prototype.valueOf() 0The conversion to number returns the wrapped primitive value:
> +Number.prototype 0Compare:
> +new Number(0) 0
> String.prototype.valueOf() ''The conversion to string returns the wrapped primitive value:
> "" + String.prototype ''Compare:
> "" + new String("") ''
> Boolean.prototype.valueOf() falseBoolean objects can be coerced to boolean (primitive) values, but the result of that coercion is always true, because converting any object to boolean is always true.
> !!Boolean.prototype true > !!new Boolean(false) true > !!new Boolean(true) trueThat is different from how objects are converted to numbers or strings: If an object wraps these primitives, the result of a conversion is the wrapped primitive.
Normal code. For normal code, use typeof and instanceof and forget about [[Class]] and Array.isArray(). You have to be aware of typeof’s quirks: That null is considered an "object" and that there are two non-primitive categories: "object" and "function". For example, a function for determining whether a value v is an object would be implemented as follows.
function isObject(v) { return (typeof v === "object" && v !== null) || typeof v === "function"; }Trying it out:
> isObject({}) true > isObject([]) true > isObject("") false > isObject(undefined) false
Code that works with values from other frames. If you expect to receive values from other frames then instanceof is not reliable, any more. You have to consider [[Class]] and Array.isArray(). An alternative is to work with the name of an object’s constructor but that is a brittle solution: not all objects record their constructor, not all constructors have a name and there is the risk of name clashes. The following function shows how to retrieve the name of the constructor of an object.
function getConstructorName(obj) { if (obj.constructor && obj.constructor.name) { return obj.constructor.name; } else { return ""; } }Another thing worth pointing out is that the name property of functions (such as obj.constructor) is non-standard and, for example, not supported by Internet Explorer. Trying it out:
> getConstructorName({}) 'Object' > getConstructorName([]) 'Array' > getConstructorName(/abc/) 'RegExp' > function Foo() {} > getConstructorName(new Foo()) 'Foo'If you apply getConstructorName() to a primitive value, you get the name of the associated wrapper type:
> getConstructorName("") 'String'That’s because the primitive value gets the property constructor from the wrapper type:
> "".constructor === String.prototype.constructor true
As a next step, you can learn more about JavaScript inheritance. The following four blog posts will get you started: