简体   繁体   中英

How best to do a JavaScript array with non-consecutive indexes?

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?

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 )

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.

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"} ;

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.

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. eg myObj['5']=poodles Basically, convert your index to a string and you're fine to use it as an object key.

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

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

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

will default to Js-Objects.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM