2009
07.29

What are literals? You use literals to represent values in JavaScript. These are fixed values, not variables, that you literally provide in your script. In this article, I am going to focus on Array and Object literals to show you the expressiveness and flexibility of JavaScript.

Object vs Array

There are a few differences between Object and Array.

  1. Their prototyped methods differs.
  2. Object does not have a built-in length, Array’s length is the number of items it has.
  3. Arrays auto increment their keys (in most cases), Object behaves like associative Arrays (meaning: you define the keys).
  4. New Objects use the {} literal and new Arrays use the [] literal.

There are a few similarities too.

  1. The typeof Array and Object is “object”. This causes headaches for many developers.
  2. You can store multiple values(data types) in Array and Object.

Object + Array vs {} + []

Similarities, read further below for proof.

  1. In their native forms. Array and Object instances share the same constructor as [] and {}.
  2. Literals are shorter representation for Array and Object instances. (Meaning: {} is equivalent to new Object() and [] is equivalent to new Array().

Differences.

  1. {} and []‘s constructor cannot be overwritten.
  2. Object and Array are constructors but {} and [] are instances.

Creating instances of Object and Array

The new operator creates an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

var obj = new Object();
var arry = new Array();

You can use literals to create instances too.

var obj = {};
//verify obj
alert(obj instanceof Object); // alerts true, revisiting below
 
var ary = [];
//verify ary
alert(ary instanceof Array); // alerts true

Populating Object and Array

You can do it during initialization.

var obj = {
  name: "Tim Burtonic",
  age: 16
};
 
alert(obj.name); // Tim Burtonic
 
var ary = ['Sugar', 'Salt', 'Oil'];
alert(ary[0]); // Sugar
 
var ary2 = new Array('Sugar', 'Salt', 'Oil');
alert(ary[2]); // Salt

You can do it after initialization.

var obj = new Object();
obj.name = "Tim Burtonic";
obj.age = 16;
obj["sex"] = "male"; // equivalent to obj.sex = "male"
 
var ary = new Array();
ary.push('Sugar', 'Salt', 'Oil');
ary[3] = 'Pepper';

Detecting Object and Array

Let’s start with the intuitive typeof method.

var obj = {};
alert(typeof obj); // alerts object
 
var ary = [];
alert(typeof ary); // alerts object

The typeof method is not helpful in this situation. Why not instanceof or constructor?

var obj = {};
alert(obj instanceof Object); // true
alert(obj.constructor === Object); // true
 
var ary = [];
alert(ary instanceof Array); // true
alert(ary.constructor === Array); // true

Now this looks promising, but let us test further. Supposing you have an iframe checking whether it’s parent’s variable is an Array.

// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
 
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false

The array and object failed the instanceof test. Why? Each frame has it’s own copy of natives and globals, while the code and functionality is the same across frames (if not overloaded), the instances are not using the same copy of constructor function, that’s why the test failed.

Around 2008, Mark Miller came out with the a great idea by scrutinizing the EcmaScript specification. Let’s see what the Miller device looks like.

function isArray ( obj ) {
  return Object.prototype.toString.call(obj) === "[object Array]";
}
 
var a = [];
isArray(a); // returns true;

This niffy piece of code works across frames too! But this turns out to be only as reliable as Object itself being native. Detecting natives is not as trivial as it seems, according to Juriy Zaytsev. Let us make this fail badly.

// native prototype overloaded, some js libraries extends them
Object.prototype.toString= function(){
  return  '[object Array]';
}
 
function isArray ( obj ) {
  return Object.prototype.toString.call(obj) === '[object Array]';
}
 
var a = {};
alert(isArray(a)); // returns true, expecting false;

Reverting to native Object and Array

The code below overloads the Object’s native prototype.

// just overwriting the prototype
Object.prototype.toString= function(o){
  return o;
}
 
// using prototype
alert(Object.prototype.toString.call(this, 'good day')); // alerts good day
 
// using new instance
var o1 = new Object();
alert(o1.toString.call(this, 'good day')); // alerts good day
 
// using literal instance
var o2 = {};
alert(o2.toString.call(this, 'good day')); // alerts good day

This overloads the Object’s constructor and native prototype.

Object = function(){}
Object.prototype.toString= function(o){ return o; }
 
// what is Object now
alert(Object);
 
// directly using prototype call
alert(Object.prototype.toString.call('good day', 'good day')); // alerts good day
 
// using new Object
var o1 = new Object();
alert(o1.toString.call('good day', 'good day')); // alerts good day
 
// using literal
var o2 = {};
alert(o2.toString.call('good day', 'good day')); // alerts [object String]
 
// Object is being reverted to it's native form;
Object = {}.constructor;
 
var o3 = new Object();
alert(o3.toString.call('good day', 'good day')); // alerts [object String]

It seems that when a native constructor is overwritten, its literal will request a new copy of the native constructor and prototypes. Using this result, I will try to reset Object and Array to their native versions.

// overwriting the constructor and prototype
// overwritten prototype
Object.prototype.toString = function(){
  return '[I am overloaded toString]';
}
 
Array.prototype.sort = function(){
  return '[I am overloaded sort]';
}
 
alert(Object.prototype.toString.call([])); // alerts [I am overloaded toString]
alert(Array.prototype.sort(['c', 'b', 'a'])); // alerts [I am overloaded sort]
 
// function declaration instead of expression must be used because we want it to execute first globally
// do not put the constructor/named function inside any closure, it will mess with the execution order
function Object(){};
Object = {}.constructor;
 
function Array(){}; 
Array = [].constructor;
 
alert(Object.prototype.toString.call([])); // alerts [object Array]
alert(Array.prototype.sort.call(['c', 'b', 'a'])); // alerts a,b,c

Here you are! I, however, have tested this piece of code in limited platforms and do not have the resources to test them. I know at least they work for grade-A browsers as published by YUI.

9 comments so far

Add Your Comment
  1. Miller device was created in 2006? I thought it was 2008.

  2. @kangax I thought I saw David Mark posted that year on comp.lang.javascript. I will need to verify that.

  3. I’m pretty sure it was 2008 : )

  4. @kangax Updated to 2008.

  5. Since 4/9/08 it has always been the Zaytsev Device to me

  6. @jdalton Same here. :D

    @karmagination Great tip! Interestingly enough, this also works for String and Function (and probably other native objects, too):

    
    String = function(){ return 'Hijacked string object!'; };
    String({});
    //=> 'Hijacked string object!'
    String = ''.constructor;
    String({});
    //=> '[object Object]'
    
    Function = function(){ return function(){}; };
    Function('arg', 'return arg;');
    //=> function(){}
    Function = (function(){}).constructor;
    Function('arg', 'return arg;');
    //=> function anonymous(arg){ return arg; }
    
  7. @Kit Nice one. There are so many things to learn from JavaScript.

  8. Nice article, thanks for sharing!