25

In JavaScript, there are objects that pretend to be arrays (or are "array-like"). Such objects are arguments, NodeLists (returned from getElementsByClassName, etc.), and jQuery objects.

When console.logged, they appear as arrays, but they are not. I know that in order to be array-like, an object must have a length property.

So I made an "object" like this:

function foo(){
    this.length = 1;
    this[0] = "bar";
}

var test = new foo;

When I console log(test), I get (as expected) a foo object. I can "convert" it to an array using

Array.prototype.slice.call(test)

But, I don't want to convert it, I want it to be array-like. How do I make an array-like object, so that when it's console.logged, it appears as an array?

I tried setting foo.prototype = Array.prototype, but console.log(new foo) still shows a foo object, and not an array.

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • 4
    *"How do I make an array-like object, so that when it's console.logged, it appears as an array?"* Just because a console isn't displaying it using Array literal syntax, or some other syntax that makes you think of an Array, doesn't mean it isn't *"array-like"*. How a console displays your data has no bearing on what that data is. –  Aug 09 '12 at 15:31
  • 1
    @amnotiam, while that is true, it's a case where I've found it useful to assist the console in assisting me. When I log an "array-like" object, I don't really care that much about the object as much as its contents. – zzzzBov Aug 09 '12 at 15:33
  • @amnotiam: I was just kinda curious how jQuery objects and `arguments` worked. I wanted to know why they are logged as arrays. :-P – gen_Eric Aug 09 '12 at 15:38
  • @zzzzBov: I don't disagree with that. I'm just saying there's a distinction between having an "array-like object", and how some console displays that object. –  Aug 09 '12 at 15:38
  • 1
    @Rocket: I understand that. But it's not how jQuery objects or `arguments` works. It's how various consoles work, which may be different from each other. Perhaps I misinterpreted your sentence above a bit. –  Aug 09 '12 at 15:39
  • @amnotiam: No, you're right. I didn't realize it was just the console doing that. I thought it was something special in the object itself. – gen_Eric Aug 09 '12 at 15:42

5 Answers5

32

Depends specifically on the console. For custom objects in Chrome's developer console, and Firebug you'll need both the length and splice properties. splice will also have to be a function.

a = {
    length: 0,
    splice: function () {}
}
console.log(a); //[]

It's important to note, however, that there is no official standard.

The following code is used by jQuery (v1.11.1) internally to determine if an object should use a for loop or a for..in loop:

function isArraylike( obj ) {
    var length = obj.length,
        type = jQuery.type( obj );

    if ( type === "function" || jQuery.isWindow( obj ) ) {
        return false;
    }

    if ( obj.nodeType === 1 && length ) {
        return true;
    }

    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

Note that it's possible to have an object that appears in the console as an array ([]) but that gets iterated over with a for..in loop in jQuery, or an object that appears as an object in the console ({}) but that gets iterated over with a for loop in jQuery.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • Neat! Adding the `splice` property *did* show it as an array! – gen_Eric Aug 09 '12 at 15:21
  • @Rocket, it's why jQuery objects look like arrays in the console, even though they don't inherit from `Array`. – zzzzBov Aug 09 '12 at 15:21
  • Then how is `arguments.splice` set to `undefined`? – gen_Eric Aug 09 '12 at 15:21
  • 3
    Anyway, its just an illusion of `console` implementations. Afterall, there is **no** `Array` in ECMAscript (when we forget about TypedArrays). – jAndy Aug 09 '12 at 15:22
  • @Rocket, type checking for specific types was used in Firebug. I don't know what chrome uses. For custom consoles, you could check for `Object.prototype.toString.call(obj) === '[object Arguments]'` in some browsers. – zzzzBov Aug 09 '12 at 15:26
  • @jAndy: What do you mean? `Array` is specified by ECMAScript. – Tim Down Aug 09 '12 at 16:11
  • @TimDown: I'm saying that the specified `Array` also just inherits from `Object` with some sugar on top. So actually, a `console` object should treat both the same on logging those things. – jAndy Aug 09 '12 at 16:37
  • It is egregious that functions and `window` are array-like. The length property on them makes no sense. – usr Mar 26 '17 at 13:56
5

The same question got into my mind as while we can use array like arguments parameter:

function arrayLike() {
  console.log(typeof arguments)
  console.log(arguments)
  console.log(Array.from(arguments))
}
arrayLike(1,2,3)

So, let's try creating our own array-like object:

let arrayLikeObject = {
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))

Obviously, there's no length property defined so our arrayLikeObject will only return an empty array. Now, let's try defining a length property:

let arrayLikeObject = {
  length: 2,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))

What if length is set different?

let arrayLikeObject = {
  length: 1,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))
 // it will only return the value from first `0: 1`

let arrayLikeObject = {
  length: 5,
  0: 1,
  1: 2
 }
 
 console.log(Array.from(arrayLikeObject))
 // other 3 values will be printed as undefined

But, I don't want to convert it...

You actually wanted to create an array, not array-like object. The array-like object must be converted like you said:

Array.prototype.slice.call(arrayLikeObject)
// Or,
[].slice.call(arrayLikeObject)

If you do try to use array methods on array-like object, then you'll get type error:

let arrayLikeObject = {
  length: 5,
  0: 1,
  1: 2
 }

 console.log(arrayLikeObject.sort())

Thus, to use the array methods on arrayLikeObject, we need to convert it into array as we did in preceding examples using Array.from.

Otherwise, you simply need to create an array:

let arr = [1,2] // I don't mean, you don't know

Other consideration:

You can't use it as constructor:

let arrayLikeObject = {
    length: 1,
    slice: function () {
      return 1
    }
}

console.log(new arrayLikeObject) // Type error

In the following snippet, the result will be [undefined] as the length property is set to 1 but there's no 0 indexed property:

let arrayLikeObject = {
  length: 1,
  slice: function () {
    return 1
  }
}
console.log(Array.from(arrayLikeObject))

But if you set the length to 0, then the result will be an empty array [] because we're telling that we don't have any values in this array-like object.

Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
3

Is this any use: extended array prototype, seems like he's doing what you did and creating the prototype as an array, but including an extra method (that may or may not work, I've not tested this):

var MyArray = function() {
};

MyArray.prototype = new Array;

MyArray.prototype.forEach = function(action) {
    for (var i = 0, l=this.length; i < l, ++i) {
        action(this[i]);
    }
};

Hope it helps in some way.

Paul Aldred-Bann
  • 5,840
  • 4
  • 36
  • 55
3

Look at this :

var ArrayLike = (function () {

 var result;

 function ArrayLike(n) {

     for (var idx = 0; idx < n; idx++) {
         this[idx] = idx + 1;
     }

     // this.length = Array.prototype.length; THIS WILL NOT WORK !

 }


 // ArrayLike.prototype.splice = Array.prototype.splice; THIS WILL NOT WORK !


 // THIS WILL WORK !
 Object.defineProperty(ArrayLike.prototype, 'length', {

     get: function() {

         var count = 0, idx = 0;

         while(this[idx]) {
             count++;
             idx++;
         }
         return count;

     }

 });


 ArrayLike.prototype.splice = Array.prototype.splice;


 ArrayLike.prototype.multiple = function () {

     for (var idx = 0 ; idx < this.length ; idx++) {

         if (result) {
             result = result * this[idx];
         } else {
             result = this[idx];
         }
     }

     return result;
 };

 return ArrayLike
 })();

var al = new ArrayLike(5);

al.__proto__ = ArrayLike.prototype;

console.log(al.length, al.multiple(), al); 

This will display in Chrome : 5 120 [1, 2, 3, 4, 5]

0

I think this is what you are looking for. Override the toString function.

foo.prototype.toString = function()
{
    return "[object Foo <" + this[0] +">]";
}
kothvandir
  • 2,111
  • 2
  • 19
  • 34
  • Not quite. What I want is when you `console.log` it, it displays like an array `[]`. In Chrome, when you `console.log(arguments)` it's looks like an array, but it's really not. – gen_Eric Aug 09 '12 at 15:29