14

jQuery objects act like arrays without polluting native prototypes. How is this achieved?

I know it's not just objects with numeric keys - so perhaps it's just a matter of providing the respective methods (something like jQuery.prototype.indexOf = Array.prototype.indexOf).

I've googled and looked at the source, but couldn't find a definitive answer.

AnC
  • 4,099
  • 8
  • 43
  • 69
  • 3
    What makes you believe it's not just objects with numeric keys? – Marius Sep 27 '09 at 12:00
  • 2
    For one thing, Firebug displays jQuery objects as arrays, but not such manually created objects - and I doubt it has special handling for jQuery. – AnC Sep 27 '09 at 12:17

2 Answers2

26

Although jQuery objects act like arrays, they are actually only array-like objects. An array-like object is an object using numeric keys and having a length property - that is the minimum needed for compatibility with the native array methods.

Because jQuery objects are only array-like and not actual Array objects, native array operations (like indexOf or reverse) cannot be called directly. You can use Array.prototype though, or extend jQuery's functionality.

$('div').reverse(); // TypeError: $("div").reverse is not a function

// we can use Array.prototype though
Array.prototype.reverse.apply($('div'));

// or we can extend jQuery very easily
$.fn.reverse = Array.prototype.reverse;
$('div').reverse(); // now it works!

You are correct in your assumption that Firebug does not include any special-casing for formatting jQuery objects. A quick search reveals a relevant post on the Firebug mailing list. Assuming the information is still correct (the post is from January) Firebug will format an object as an array if it has a finite length and a splice method.

JQuery fulfils both of these criteria, but their implementation of splice is nothing more than a direct copy of the native Array method. It is undocumented, which means it's either only for internal use, or perhaps added solely for the purpose of tricking Firebug into formatting jQuery objects nicely.

Alex Barrett
  • 16,175
  • 3
  • 52
  • 51
  • This is excellent - thanks! I'd hesitate to add a splice method just to make it look pretty in Firebug, but that's just a minor detail. (PS: There's a typo in your post; you mention "slice" instead of "splice".) – AnC Sep 27 '09 at 16:09
  • Thank you - corrected. I only suggest that the `splice` method was added to jQuery specifically for Firebug because jQuery's 1.2 branch does _not_ expose such a method. Indeed, I do remember a short period of time in which my jQuery objects were not being formatted as `Arrays` in Firebug. Of course, the method's introduction in 1.3 may have just coincidence. – Alex Barrett Sep 29 '09 at 09:55
  • 1
    Thank you – this was interesting to chance upon. I note Safari’s web inspector seems to use the same criteria – `length` object and `splice` method – to format objects as arrays: `var o; o = { length: 2, splice: function(x) { } };` => `[ ]` (tho’ firebug rigorously prints: `[undefined, undefined]`). – Benji XVI Jul 23 '11 at 11:32
5

Look in jquery code (development), line 139:

// Force the current matched set of elements to become
// the specified array of elements (destroying the stack in the process)
// You should use pushStack() in order to do this, but maintain the stack
setArray: function( elems ) {
    // Resetting the length to 0, then using the native Array push
    // is a super-fast way to populate an object with array-like properties
    this.length = 0;
    Array.prototype.push.apply( this, elems );
        return this;
},

This is because any result of jquery queries is array.

Anatoliy
  • 29,485
  • 5
  • 46
  • 45
  • Thanks. I'd seen this as well, but it doesn't seem to suffice: var MyObj = function(args) { this.length = 0; Array.prototype.push.apply(this, arguments); return this; }; var obj = new MyObj("hello", "world"); – AnC Sep 27 '09 at 12:35
  • 1
    Don't return `this` from a constructor! Also, `var` sets a variable, but it does not return a value; if you are executing that snippet in a Firebug-like tool: `var MyObj = function(args) { this.length = 0; Array.prototype.push.apply(this, arguments); }; obj = new MyObj("hello", "world");` – Alex Barrett Sep 27 '09 at 13:50
  • I'm confused: If it's not a constructor, "this" won't be the object (just window) though!? Can you provide a working sample? – AnC Sep 27 '09 at 14:36
  • `MyObj` _is_ a constructor, that's the point! And that is a working sample :) – Alex Barrett Sep 27 '09 at 14:54
  • Sorry, I misread your earlier statement. However, your code there doesn't seem to work for me: obj.indexOf is undefined - also, obj is not being displayed as an array in Firebug. Am I missing something? – AnC Sep 27 '09 at 15:04
  • I will post an answer explaining Firebug's behaviour, give me a few minutes. – Alex Barrett Sep 27 '09 at 15:13