Showing posts with label array. Show all posts
Showing posts with label array. Show all posts

July 21, 2011

How to detect a JavaScript array

"Piece of cafe" I bet you're thinking, oh I wish it were that easy.

But fear not, JavaScript does have the typeof operator (returns a string) which is very useful for type-checking:
var str = 'hello',
    num = 64,
    bool = false,
    func = function () {},
    undef;

typeof str; // "string"
typeof num; // "number"
typeof bool; // "boolean"
typeof func; // "function"
typeof undef; // "undefined"
Like shown in the previous code, it works fine for most things. However, it's a little flawed:
var n = null,
    str = new String('hello'), // any wrapper function or custom constructor
    arr = [1, 2, 3];

typeof n; // "object", which is wrong
typeof str; // "object", which is correct but not very useful.
typeof arr; // "object", which is correct but not very useful.

constructor property
Every created object has a constructor property that points to the function that created the object's prototype. You can leverage that reference to do some accurate detections:
var str = new String('hello'),
    arr = [1, 2, 3],
    obj,
    Person = function (name) {
        this.name = name;
    };

obj = new Person('John');

obj.constructor === Person; // true
str.constructor === String; // true
arr.constructor === Array; // true
isArray method
ECMAScript 5 defines a new method for the Array function called isArray(). If it's not implemented by the browser you can define it like this:
if (!Array.hasOwnProperty('isArray')) {
    Array.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };
}
Invoking the toString() method of Object, gets us a string representation of the object, for arrays it's [object Array]:
var arr1 = [1, 2, 3],
    arr2 = new Array(1, 2, 3);

Array.isArray(arr1); // true
Array.isArray(arr2); // true
Array.isArray(new Number('12')); // false
Array.isArray({}); // false

Sources:
typeof operator (MDC)
constructor property (MDC)

March 28, 2011

HTMLCollections & NodeLists

Most of us believed, at least for some time, that in our DOM Scripting, we always dealt with arrays in our JavaScript:
var my_links = document.getElementsByTagName('a'); // we have three links
alert(my_links.length) // outputs "3"

We later found out that the things we thought were arrays, were instead array-like objects. But how exactly are they like arrays? Those "array-like" objects/elements/things, most of the time, are either HTMLCollections or NodeLists, not native JavaScript array objects. Take a look at what the specification says of them, the keyword is live:

"An HTMLCollection is a list of nodes. Collections in the HTML DOM are assumed to be live meaning that they are automatically updated when the underlying document is changed."
- DOM Level 1

"The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live."
- DOM Level 3

But what does that mean? It means that live collections, if modified, are updated as the program runs. For example, this is an infinte loop:

var i, j,
    my_links = document.getElementsByTagName('a'); // we have three links

for (i = 0, j = my_links.length; i < j; i += 1 ) {
    document.body.appendChild(document.createElement('a'));
}

We're getting our collection (of three links) and then for each link that we have, we're going to append another link to the body. So why is this infinite? Because the collection is live, which means that not only will i increment, j also will, so naturally the loop will keep going.

So why are they called array-like objects? If it looks like an array and acts like an array, then it must be an array, right? Wrong, DOM collections look like arrays because:
  • They have an associated index to each value in the container. But that's something an object can have too:
    var my_obj = {
        0: 'zero',
        1: 'one',
        2: 'two',
        3: 'three'
    };
    
    alert( my_obj[0] ); // 'zero'
    alert( my_obj[3] ); // 'three'
    
    The alert statements might look like they want the elements with index 0 and 3 but you're really getting the value from the property named 0 and 3.
  • They have a length property. This is deceptive because arrays have this same property, but so do HTMLCollections and NodeLists. But, because these are not true arrays they do not have push, concat, splice or any of the other array methods.

Be prepared, these are some of the DOM methods (that I know of) that return an HTMLCollection or NodeList:

// DOM Level 1/HTML 4.0
// ---------------------------
// Return an HTMLCollection
document.anchors
document.applets
document.forms 
document.images 
document.links

document.getElementsByName

formElement.elements
selectElement.options
tableElement.rows
tableElement.tBodies
tableRowElement.cells

// Not part of any standard
document.embeds
document.plugins

// DOM Level 2
// ------------------
// Return a NodeList
Node.childNodes

document.getElementsByName
document.getElementsByTagName
document.getElementsByTagNameNS
document.getElementsByName

element.getElementsByTagName
element.getElementsByTagNameNS

// WHATWG Web Applications 1.0
// ---------------------------
// Return a NodeList
document.getElementsByClassName
element.getElementsByClassName

Throughout this post I've been talking about NodeLists as such and not as live NodeLists because they are inherintely live. There's an exception to this, there are static NodeLists that act as snapshots and do not update when the document is modified:

// Selectors API Level 1
// ---------------------
// Return a static NodeList
document.querySelectorAll
element.querySelectorAll

In conclusion, I think it's important to know the differences between a live DOM collection a true JavaScript array, it's also an important thing to be aware of because you'll eventually interact with these.

I know so far I've been talking mostly about the DOM (sorry, this wasn't the exception) but you cannot say that this was a boring topic, or was it?

Thanks for reading and let me know your comments.

Sources:
Why is getElementsByTagName() faster than querySelectAll()?
Speed Up Your JavaScript (video)
HTMLCollection - MDN Doc Center, NodeList - MDN Doc Center
DOM Level 1 Specification, DOM Level 2 Specification, DOM Level 3 Specification