23

I'm writing a Google Chrome extension, in JavaScript, and I want to use an array to store a bunch of objects, but I want the indexes to be specific non-consecutive ID numbers.

(This is because I need to be able to efficiently look up the values later, using an ID number that comes from another source outside my control.)

For example:

var myObjects = [] ;

myObjects[471] = {foo: "bar"} ;

myObjects[3119] = {hello: "goodbye"}

When I do console.log(myObjects), in the console I see the entire array printed out, with all the thousands of 'missing' indexes showing undefined.

My question is: does this matter? Is this wasting any memory?

And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?

I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?

callum
  • 34,206
  • 35
  • 106
  • 163

7 Answers7

16

First of all, everyone, please learn that what the for-in statement does is called enumeration (though it's an IterationStatement) in order to differentiate from iteration. This is very important, because it leads to confusion especially among beginners.

To answer the OP's question: It doesn't take up more space (test) (you could say it's implementation dependent, but we're talking about a Google Chrome Extension!), and it isn't slower either (test).

Yet my advice is: Use what's appropriate! In this situation: use objects!

What you want to do with them is clearly a hashing mechanism, keys are converted to strings anyway so you can safely use object for this task.

I won't show you a lot of code, other answers do it already, I've just wanted to make things clear.

// keys are converted to strings 
// (numbers can be used safely)
var obj = {}
obj[1] = "value"
alert(obj[1])   // "value"
alert(obj["1"]) // "value"

Note on sparse arrays

The main reason why a sparse array will NOT waste any space is because the specification doesn't say so. There is no point where it would require property accessors to check if the internal [[Class]] property is an "Array", and then create every element from 0 < i < len to be the value undefined etc. They just happen to be undefined when the toString method is iterating over the array. It basically means they are not there.

11.2.1 Property Accessors

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Let baseReference be the result of evaluating MemberExpression.
  2. Let baseValue be GetValue(baseReference).
  3. Let propertyNameReference be the result of evaluating Expression.
  4. Let propertyNameValue be GetValue(propertyNameReference).
  5. Call CheckObjectCoercible(baseValue).
  6. Let propertyNameString be ToString(propertyNameValue).
  7. If the syntactic production that is being evaluated is contained in strict mode code, let strict be true, else let strict be false.
  8. Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

The production CallExpression : CallExpression [ Expression ] is evaluated in exactly the same manner, except that the contained CallExpression is evaluated in step 1.

ECMA-262 5th Edition (http://www.ecma-international.org/publications/standards/Ecma-262.htm)

gblazex
  • 49,155
  • 12
  • 98
  • 91
  • "The main reason why a sparse array will NOT waste any space is because the specification doesn't say so" - depends on the context, they *do* waste space just not to *store* them, but you're using a larger buffer when iterating over that mostly empty array and you get the extra CPU cycles to boot. – Nick Craver Jan 23 '11 at 14:12
  • Just a quick question/observation. It appears that with non-associative arrays (indexed/numeric/true arrays), numeric strings are converted to integers?! i.e. arr['1'] ==> arr[1] ?!! – arxpoetica Jul 23 '13 at 17:57
5

You can simply use an object instead here, having keys as integers, like this:

var myObjects = {};
myObjects[471] = {foo: "bar"};
myObjects[3119] = {hello: "goodbye"};

This allows you to store anything on the object, functions, etc. To enumerate (since it's an object now) over it you'll want a different syntax though, a for...in loop, like this:

for(var key in myObjects) {
  if(myObjects.hasOwnProperty(key)) {
    console.log("key: " + key, myObjects[key]);
  }
}

For your other specific questions:

My question is: does this matter? Is this wasting any memory?

Yes, it wastes a bit of memory for the allocation (more-so for iterating over it) - not much though, does it matter...that depends on how spaced out the keys are.

And even if it's not wasting memory, surely whenever I loop over the array, it wastes CPU if I have to manually skip over every missing value?

Yup, extra cycles are used here.

I tried using an object instead of an array, but it seems you can't use numbers as object keys. I'm hoping there's a better way to achieve this?

Sure you can!, see above.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • missing a closing parenthesis after `.hasOwnProperty(key)` :) – Russ Cam Jan 22 '11 at 22:29
  • 1
    Regarding *"Yes, it wastes a bit of memory"*, are you certain? My understanding is that Arrays are sparse. After all, they're ultimately just enhanced Objects, so two properties means just those two. Or am I mistaken? – user113716 Jan 22 '11 at 22:34
  • being an object, it's keys are converted to string before being evaluated...so yes, they can be integers. for example, try this: (function(){var obj = {"13":{}}; return obj[13] === obj["13"];})() – gion_13 Jan 22 '11 at 22:40
  • `for..in` is **enumerating** the objects properties, and thus we shouldn't call it *iteration*. Also you showed no evidence for the extra memory consumption, only stated your false assumptions (without clarifying that those are only assumptions) – gblazex Jan 22 '11 at 23:56
  • @patrick, yes arrays in JS are sparse. Further, a for in loop will not iterate over the unused array indices, so there really is no "skipping over values". – David Tang Jan 23 '11 at 00:08
  • @patrick - I meant it uses more memory for the iteration of the empty array itself, not the the array uses more memory (though it does, slightly). – Nick Craver Jan 23 '11 at 01:03
  • 1
    @galambalazs - I agree, I meant what was *previously* iteration, tried to make this a bit clearer in the answer. – Nick Craver Jan 23 '11 at 01:04
3

I would use an object to store these. You can use numbers for properties using subscript notation but you can't using dot notation; the object passed as the key using subscript notation has toString() called on it.

var obj = {};
obj[471] = {foo: "bar"} ;
Russ Cam
  • 124,184
  • 33
  • 204
  • 266
2

It would be implementation dependent, but I don't think you need to worry about wasted memory for the "in between" indices. The developer tools don't represent how the data is necessarily stored.

Regarding iterating over them, yes, you would be iterating over everything in between when using a for loop.

If the sequential order isn't important, then definitely use a plain Object instead of an Array. And yes, you can use numeric names for the properties.

var myObjects = {} ;

myObjects["471"] = {foo: "bar"} ;

myObjects["3119"] = {hello: "goodbye"};

Here I used Strings for the names since you said you were having trouble with the numbers. They ultimately end up represented as strings when you loop anyway.

Now you'll use a for-in statement to iterate over the set, and you'll only get the properties you've defined.


EDIT:

With regard to console.log() displaying indices that shouldn't be there, here's an example of how easy it is to trick the developer tools into thinking you have an Array.

var someObj = {};

someObj.length = 11;
someObj.splice = function(){};

someObj[10] = 'val';

console.log(someObj);

Clearly this is an Object, but Firebug and the Chrome dev tools will display it as an Array with 11 members.

[undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, "val"]

So you can see that the console doesn't necessarily reflect how/what data is actually stored.

user113716
  • 318,772
  • 63
  • 451
  • 440
2

As I understand it from my reading of Crockford's "The Good Parts," this does not particularly waste memory, since javascript arrays are more like a special kind of key value collection than an actual array. The array's length is defined not as the number of addresses in the actual array, but as the highest-numbered index in the array.

But you are right that iterating through all possible values until you get to the array's length. Better to do as you mention, and use an object with numeric keys. This is possible by using the syntax myObj['x']=y where x is the symbol for some integer. e.g. myObj['5']=poodles Basically, convert your index to a string and you're fine to use it as an object key.

traffichazard
  • 1,247
  • 1
  • 9
  • 13
0

You could attempt to do something like this to make it loud and clear to the JIST compiler that this is a more objecty-ish array like so:

    window.SparseArray = function(len){
      if (typeof len === 'number')
        if (0 <= len && len <= (-1>>>0))
          this.length = len;
        else
          new Array(len); // throws an Invalid array length type error
      else
        this.push.apply(this, arguments);
    }
    window.SparseArray.prototype = Array.prototype
Jack G
  • 4,553
  • 2
  • 41
  • 50
0

I would simply use a constant prefix, to avoid such problems.

var myObjects = {};
myObjects['objectId_'+365] = {test: 3};

will default to Js-Objects.

FloydThreepwood
  • 1,587
  • 14
  • 24