JavaScript objects aren't hash tables, and on a modern engine hash tables are only used in their implementation if you delete properties from them. Otherwise, they're instances of highly-optimized runtime-generated classes. None of which really matters to the problem at hand. :-)
Assuming you want an ongoing link between the 0
property and one of the named properties, there's just no way to do this in ES5 and earlier. Instead, you'll need getter/setter methods for the indexed access:
Object.defineProperty(foo, "item", {
value: function(index, value) {
var key = Object.keys(this).sort(orderingFunction)[index];
if (arguments.length < 2) {
// Getter
return this[key];
}
// Setter
this[key] = value;
return this;
}
});
...where orderingFunction
is the function that defines your concept of the order of the keys. (In ES5 and below, properties had no order. While in ES2015 and above properties do have an order, it's A) Not useful, and B) Not guaranteed to be the order returned by Object.keys
.)
Usage:
foo.item(0); // "value"
foo.item(0, "new value");
Or if you prefer, use separate functions for the getter and setter.
Note that this is fairly expensive, what with all that sorting.
There is a way to do it in ES2015 and above: a Proxy
object, which can do two key things here:
Implement a default property access function that you can use for numeric access
Allow you to maintain your keys array without having to constantly re-sort it
// REQUIRES ES2015+ SUPPORT IN YOUR BROWSER
let foo = {
question: "Life, the Universe, and Everything!",
answer: 42
};
let rexNumber = /^\d+$/;
let fooProxy = indexedProxy(foo);
console.log(fooProxy[0]); // 42
fooProxy.answer = 67;
console.log(fooProxy[0]); // 67
fooProxy.bar = "foo";
console.log(fooProxy[1]); // "foo"
function indexedProxy(obj) {
// An array of the current keys
let keys = getKeys(foo);
// Return the proxy
return new Proxy(obj, {
// Called when any property is gotten from the target
get(target, property) {
if (rexNumber.test(property)) {
// Indexed access
let key = keys[property];
return key === undefined ? undefined : target[key];
}
// Normal access
return target[property];
},
// Called when any property is set on the target
set(target, property, value) {
if (rexNumber.test(property)) {
// Indexed access
let key = keys[property];
if (key === undefined) {
throw new Error(`No property for index #{property}`);
}
target[key] = value;
} else {
// Normal access, do we know the key?
let newProp = !keys.includes(property);
target[property] = value;
if (newProp) {
// Rebuild our array
keys = getKeys(target);
}
}
}
});
}
function getKeys(obj) {
// Let's go for alphabetical
return Object.keys(obj).sort((a, b) => a.localeCompare(b));
}
Proxies are also not cheap, but hopefully would perform better than an array you constantly have to generate and sort.
Obviously the above is just an example, not a highly-optimized general-purpose tool. It doesn't use the correct definition of an array index (details buried in this answer), it uses a simpler "is it all digits?" definition. It only handles an objects own, enumerable properties; if you want to handle inherited properties or ones marked non-enumerable, that's possible, it just needs tweaking thea above.