41

Can an array in JavaScript be associative AND indexed?

I'd like to be able to lookup an item in the array by its position or a key value.

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
puffpio
  • 3,402
  • 6
  • 36
  • 41

11 Answers11

30

There are no such things as associative arrays in Javascript. You can use object literals, which look like associative arrays, but they have unordered properties. Regular Javascript arrays are based on integer indexes, and can't be associative.

For example, with this object:

var params = {
    foo: 1,
    bar: 0,
    other: 2
};

You can access properties from the object, for example:

params["foo"];

And you can also iterate over the object using the for...in statement:

for(var v in params) {
    //v is equal to the currently iterated property
}

However, there is no strict rule on the order of property iteration - two iterations of your object literal could return the properties in different orders.

Alex Rozanski
  • 37,815
  • 10
  • 68
  • 69
  • 1
    Actually, JavaScript arrays can have non-integer indices as well. It just doesn't have much in the way of methods for dealing with them. – Nosredna Jul 02 '09 at 21:23
  • 3
    You mean like a string? in that case it isn't an array, just an object with properties. – roryf Jul 02 '09 at 22:41
  • 3
    Nosredna: JS arrays do not have key indices, those JS objects are considered object literals. so: foo["bar"] = 1; foo.bar === foo["bar"]; //true foo["bar"] === foo[0]; //false This is one of the many subtle quirks about JS that throw people off easily. – linusthe3rd Jul 03 '09 at 00:48
  • 7
    JavaScript Objects, including Arrays, have *only* string indices. `a[1]` is actually saying `a['1']`. – bobince Apr 04 '10 at 21:49
  • 1
    The latest MDN documentation makes it quiet clear that Array index must be integers. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array – gujralam Sep 06 '20 at 09:15
22

After reading the Wikipedia definition of associative array, I'm going to break with traditional JavaScript lore and say, "yes, JavaScript does have associative arrays." With JavaScript arrays, you can add, reassign, remove, and lookup values by their keys (and the keys can be quoted strings), which is what Wikipedia says associative arrays should be able to do.

However, you seem to be asking something different--whether you can look up the same value by either index or key. That's not a requirement of associative arrays (see the Wikipedia article.) Associative arrays don't have to give you the ability to get a value by index.

JavaScript arrays are very closely akin to JavaScript objects.

  arr=[];
  arr[0]="zero";
  arr[1]="one";
  arr[2]="two";
  arr["fancy"]="what?";

Yes, that's an array, and yes, you can get away with non-numeric indices. (If you're curious, after all this, arr.length is 3.)

In most cases, I think you should stick to numeric indices when you use arrays. That what most programmers expect, I think.

The link is to my blog post about the subject.

Nosredna
  • 83,000
  • 15
  • 95
  • 122
  • You were right when you said "And you can have an array with non-numeric indices". It slipped my mind somehow, but I know this fact. – Ionuț G. Stan Jul 02 '09 at 21:28
  • 15
    To be pedantic, "fancy" is not an index in the array, but an attribute of the array instance object. – BaroqueBobcat Jul 02 '09 at 22:39
  • Yeah, good point. That's why I say that arrays are closely related to objects in JavaScript. – Nosredna Jul 02 '09 at 22:46
  • 4
    @BaroqueBobcat: to be really pedantic :) indices in the array are just properties ("attributes") of the array instance object; an array just treats specially those properties which are the string form of an integer with regards to the length property. – Miles Jul 03 '09 at 02:52
  • 6
    It's not pedantic at all. It's an incredibly important distinction, made evident when you start trying to serialise your array into, say, JSON. The value with a non-numeric key is _not_ part of the array data. (And strictly speaking, you're breaking your array by not using `.push()`.) – Lightness Races in Orbit Aug 04 '11 at 15:42
  • 3
    I downvoted this answer, because while it might be possible to use syntaxt that looks like an array, this is not proper javascript nor a proper array. Your answer is good for reference, but should have a clear reminder that this is NOT how you'd conventionally use arrays. – kontur Aug 10 '12 at 09:15
  • Arrays should be able to be indexed so when it's iterated, it is returned in order, but here it's not the case. – z.a. Dec 12 '15 at 00:11
  • I know this is an old post, but to be clear, JavaScript does not have associative arrays and you cannot pass a string index to an array. What you are talking about here is an object with keys. – Scott Marcus Sep 28 '20 at 21:38
9

Native JS objects only accept strings as property names, which is true even for numeric array indices; arrays differ from vanilla objects only insofar as most JS implementations will store numerically indexed properties differently (ie in an actual array as long as they are dense) and setting them will trigger additional operations (eg adjustment of the length property).

If you're looking for a map which accepts arbitrary keys, you'll have to use a non-native implementation. The script is intended for fast iteration and not random-access by numeric indices, so it might nor be what you're looking for.

A barebones implementation of a map which would do what you're asking for could look like this:

function Map() {
    this.length = 0;
    this.store = {};
}

Map.prototype.get = function(key) {
    return this.store.hasOwnProperty(key) ?
        this.store[key] : undefined;
};

Map.prototype.put = function(key, value, index) {
    if(arguments.length < 3) {
        if(this.store.hasOwnProperty(key)) {
            this.store[key].value = value;
            return this;
        }

        index = this.length;
    }
    else if(index >>> 0 !== index || index >= 0xffffffff)
        throw new Error('illegal index argument');

    if(index >= this.length)
        this.length = index + 1;

    this[index] = this.store[key] =
        { index : index, key : key, value : value };

    return this;
};

The index argument of put() is optional.

You can access the values in a map map either by key or index via

map.get('key').value
map[2].value
Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • +1, but I would lose "numerically indexed properties will be stored differently": that's not necessarily the case, and isn't required by the spec. – Miles Jul 03 '09 at 02:48
  • How do you put? map.put('key','value') and map[2].value = val ? – Chris Jan 14 '11 at 17:50
  • `map.put('key','value')` for appending a value and `map.put('key','value', 42)` for inserting a value at a given position; `map[42].value = 'value'` will also work (ie replace the value of an existing key-value-pair), but you can't change the key or index of the entry or add new entries this way – Christoph Jan 14 '11 at 23:48
7
var myArray = Array();
myArray["first"] = "Object1";
myArray["second"] = "Object2";
myArray["third"] = "Object3";

Object.keys(myArray);              // returns ["first", "second", "third"]
Object.keys(myArray).length;       // returns 3

if you want the first element then you can use it like so:

myArray[Object.keys(myArray)[0]];  // returns "Object1"
Tom Prats
  • 7,364
  • 9
  • 47
  • 77
5

The order in which objects appear in an associative javascript array is not defined, and will differ across different implementations. For that reason you can't really count on a given associative key to always be at the same index.

EDIT:

as Perspx points out, there aren't really true associative arrays in javascript. The statement foo["bar"] is just syntactic sugar for foo.bar

If you trust the browser to maintain the order of elements in an object, you could write a function

function valueForIndex(obj, index) {

    var i = 0;
    for (var key in obj) {

        if (i++ == index)
            return obj[key];
    }
}
Matt Bridges
  • 48,277
  • 7
  • 47
  • 61
  • 3
    Although this is a true, in practice all major browsers loop over the properties of an object in the order in which they were defined. This is not in the spec, of course, but it's worth a mention. – Paolo Bergantino Jul 02 '09 at 21:05
  • JavaScript does not have associative arrays. Your answer is talking about objects, not arrays. – Scott Marcus Sep 28 '20 at 21:36
1
var stuff = [];
stuff[0] = "foo";
stuff.bar = stuff[0]; // stuff.bar can be stuff["bar"] if you prefer
var key = "bar";
alert(stuff[0] + ", " + stuff[key]); // shows "foo, foo"
NickFitz
  • 34,537
  • 8
  • 43
  • 40
  • but then stuff.length would not be 1 anymore since you added .bar right? thus looping through by index wouldnt really work anymore.. – puffpio Jul 09 '09 at 20:27
  • 1
    No - adding a named property doesn't increase the length; only adding an element by numeric index increases length. Adding "stuff[1000] = 'blah'" would cause the length to increase to 1001, even though only two numerically-indexed elements and one named property have actually been added. Fun isn't it ;-) – NickFitz Jul 10 '09 at 11:06
1

I came here to wanting to know if this is bad practice or not, and instead found a lot of people appearing not to understand the question.

I wanted to have a data structure that was ordered but could be indexed by key, so that it wouldn't require iteration for every lookup.

In practical terms this is quite simple, but I still haven't read anything on whether it's a terrible practice or not.

var roygbiv = [];
var colour = { key : "red", hex : "#FF0000" };
roygbiv.push(colour);
roygbiv[colour.key] = colour;
...
console.log("Hex colours of the rainbow in order:");
for (var i = 0; i < roygbiv.length; i++) {
    console.log(roygbiv[i].key + " is " + roygbiv[i].hex);
}

// input = "red";
console.log("Hex code of input colour:");
console.log(roygbiv[input].hex);

The important thing is to never change the value of array[index] or array[key] directly once the object is set up or the values will no longer match. If the array contains objects you can change the properties of those objects and you will be able to access the changed properties by either method.

Joel Roberts
  • 149
  • 1
  • 9
  • This is the approach I took, but with a syntax showing the association at assignment which I found more to my liking `aSignals[aSignals.length] = aSignals['fooBar'] = { 'sKey': 'foobBar', ... }` – Daniel Sokolowski Apr 24 '18 at 03:28
0

Although I agree with the answers given you can actually accomplish what you are saying with getters and setters. For example:

var a = [1];
//This makes a["blah"] refer to a[0]
a.__defineGetter__("blah", function(){return this[0]});
//This makes a["blah"] = 5 actually store 5 into a[0]
a.__defineSetter__("blah", function(val){ this[0] = val});

alert(a["blah"]); // emits 1
a["blah"] = 5;
alert(a[0]); // emits 5

Is this what you are looking for? i think theres a different more modern way to do getters and setters but cant remember.

Ryan
  • 2,755
  • 16
  • 30
  • Oh and if you are going to do this in a loop you will likely need closures btw... just saying. – Ryan Apr 13 '12 at 23:34
0

The tide has changed on this one. Now you can do that... and MORE! Using Harmony Proxies you could definitely solve this problem in many ways.

You'll have to verify that your targeted environments support this with maybe a little help from the harmony-reflect shim.

There's a really good example on the Mozilla Developer Network on using a Proxy to find an array item object by it's property which pretty much sums it up.

Here's my version:

  var players = new Proxy(
  [{
    name: 'monkey',
    score: 50
  }, {
    name: 'giraffe',
    score: 100
  }, {
    name: 'pelican',
    score: 150
  }], {
    get: function(obj, prop) {
      if (prop in obj) {
        // default behavior
        return obj[prop];
      }
      if (typeof prop == 'string') {

        if (prop == 'rank') {
          return obj.sort(function(a, b) {
            return a.score > b.score ? -1 : 1;
          });
        }

        if (prop == 'revrank') {
          return obj.sort(function(a, b) {
            return a.score < b.score ? -1 : 1;
          });
        }

        var winner;
        var score = 0;
        for (var i = 0; i < obj.length; i++) {
          var player = obj[i];
          if (player.name == prop) {
            return player;
          } else if (player.score > score) {
            score = player.score;
            winner = player;
          }
        }

        if (prop == 'winner') {
          return winner;
        }
        return;
      }

    }
  });

  console.log(players[0]); // { name: 'monkey', score: 50 }
  console.log(players['monkey']); // { name: 'monkey', score: 50 }
  console.log(players['zebra']); // undefined
  console.log(players.rank); // [ { name: 'pelican', score: 150 },{ name: 'giraffe', score: 100 }, { name: 'monkey', score: 50 } ]
  console.log(players.revrank); // [ { name: 'monkey', score: 50 },{ name: 'giraffe', score: 100 },{ name: 'pelican', score: 150 } ]
  console.log(players.winner); // { name: 'pelican', score: 150 }
Quickredfox
  • 1,428
  • 14
  • 20
0

The latest MDN documentation makes it quiet clear that Array index must be integers. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

let arr=[];
 arr[0]="zero";
 arr[1]="one";
 arr[2]="two";
 arr["fancy"]="what?";

//Arrays cannot use strings as element indexes (as in an associative array) but must use integers. 
//Setting non-integers using bracket notation will not set an element to the Array List itself
//A non-integer will set a variable associated with that ARRAY Object property collection 
let denseKeys = [...arr.keys()];
console.log(denseKeys);//[ 0, 1, 2 ]
console.log("ARRAY Keys:"+denseKeys.length);//3


let sparseKeys = Object.keys(arr);
console.log(sparseKeys);//[ '0', '1', '2', 'fancy' ]
console.log("Object Keys:"+sparseKeys.length);//4

const iterator = arr.keys();
for (const key of iterator) {
  console.log(key);//0,1,2
}
gujralam
  • 187
  • 1
  • 12
-2

Yes.

test = new Array();
test[0] = 'yellow';
test['banana'] = 0;
alert(test[test['banana']]);