Is there a way to bulk set associative array keys and values in JavaScript, similar to PHP's shorthand array declaration below?
$array = [
'foo' => 'val1',
'bar' => 'val2',
'baz' => 'val3'
];
The only way I can think of to declare associative arrays in JS is something like this:
var newArray = new Array();
newArray['foo'] = 'val1';
newArray['bar'] = 'val2';
newArray['baz'] = 'val3';
Or something like this:
var newArray = new Array(),
keys = ['foo', 'bar', 'baz'],
vals = ['val1', 'val2', 'val3'];
for(var i = 0; i < keys.length; i++) {
newArray[ keys[i] ] = vals[i];
}
That last one can cause a mess in certain cases, but I wanted to list it since it's doable.
If there isn't a way like that top example, then that's fine. It would've been nice. I tried using a JS object literal already with jQuery like this:
var myObject = {
foo: $('foo'),
bar: $(this.foo).find('bar'),
baz: $(this.bar).data('baz')
}
Unfortunately it seems that JS object literals don't allow evaluating code to set values for their properties. If I turned those properties into methods that returned values instead of permanently storing the values/references in the properties, then they would have to run every time they're called; I really don't want to do that if possible.
Thanks in advance.
PHP's "associative arrays" — an ordered set of name/value pairs — are a fairly rare data structure, particularly rare to find as a feature of the language rather than as a library class. JavaScript doesn't have them — yet. ES6 will introduce better iteration semantics to the language generally, and a standard library Map
type that takes advantage of those semantics so that when iterated, it visits entries in key insertion order; more below.
Until ES6, you can use objects, as you seem to know, like so:
var obj = {
foo: 'val1',
bar: 'val2',
baz: 'val3'
};
...but the properties have no order. If order is important, you'd have to track the order separately, perhaps by having the keys in an array:
var keys = ['foo', 'bar', 'baz'];
var obj = {
foo: 'val1',
bar: 'val2',
baz: 'val3'
};
var i;
for (i = 0; i < keys.length; ++i) {
console.log(obj[keys[i]]);
}
This is less delicate than parallel arrays, because you only have to worry about the order of one array.
ES6's Map
defines that when iterated, the map's entries are visited in the order in which the keys were inserted. You can also initialize a Map
instance by using an array of key/value
arrays, like so:
var map = new Map([
["foo", "val1"],
["bar", "val2"],
["baz", "val3"]
]);
So that will be the equivalent form when it's widely-supported.
More about Map
in this article (and the draft spec).
Unfortunately it seems that JS object literals don't allow evaluating code to set values for their properties
Yes, they do. In your example, the foo
property will be a jQuery object with a set of elements matching the CSS selector foo
(a set which is probably empty, as there is no foo
element).
Example:
var obj = {
foo: 6 * 7
};
console.log(obj.foo); // 42
You can't (yet) use an expression for the property name in a property initializer like that, just the value. In ES6, you'll be able to use an expression for the property name as well:
// As of ES6
var obj = {
['f' + 'o' + 'o']: 6 * 7
};
console.log(obj.foo); // 42
Meanwhile, in ES5 we can do it, but not in the initializer:
// Now
var obj = {};
obj['f' + 'o' + 'o'] = 6 * 7;
console.log(obj.foo); // 42
I tried using a JS object literal already with jQuery like this:
var myObject = { foo: $('foo'), bar: $(this.foo).find('bar'), baz: $(this.bar).data('baz') }
There are a couple of problems there:
$('foo')
looks for elements with the tag foo
— there is no foo
HTML tag. Perhaps you meant #foo
or .foo
(or perhaps foo
was just meant as a stand-in for div
or span
or whatever).
Your foo
property's value is already a jQuery object, no need to wrap it in another $()
call.
this
has no special meaning within an object initializer, it's the same as this
outside the object initializer, not a reference to the object being initialized. Malk's suggestion would also not work, because as of when the properties are being initialized, myObject
hasn't had any value assigned to it yet. Here's how your overall expression above is evaluated:
Prior to any step-by-step code in the scope, a variable called myObject
is created with the value undefined
.
When the expression is reached in the step-by-step execution of the code, start the assignment expression by evaluating the right-hand side (the bit after the =
).
To start the object initializer expression, a new object is created but not stored anywhere.
Each property initializer in the object initializer is processed in source code order (yes, that's guaranteed by the spec), like so:
Evaluate the expression defining its property.
Create the property on the object using the given key and the evaluated value.
When the object initializer is complete, the resulting value (the object reference) is assigned to myObject
.
To refer to properties of the object from the expressions defining other properties of the object, you have to do a series of statements rather than a single statement:
var myObject = {
foo: $('foo') // or $('#foo') or $('.foo') or whatever it is
};
myObject.bar = myObject.foo.find('bar');
myObject.baz = myObject.bar.data('baz');
Alternately, you could do something convoluted with temporary variables:
var foo, bar, myObject = {
foo: (foo = $('foo')),
bar: (bar = foo.find('bar')),
baz: bar.data('baz')
};
...because the result of an assignment expression is the value that was assigned. (No enduring connection between the properties and the variables is maintained.) But then you have those temp variables lying around. You could wrap the entire thing in a function so the temp vars are only around temporarily:
var myObject = (function() {
var foo, bar;
return {
foo: (foo = $('foo')),
bar: (bar = foo.find('bar')),
baz: bar.data('baz')
};
})();
...but that's getting pretty convoluted. :-)
Re your comment below:
are there any limits on what kind of expressions can be evaluated into property values?
No, the expression processing for the values of property initializers is exactly the same as expression handling elsewhere in the scope containing the object initializer that the property initializer is part of.
Here's an example grabbing some DOM elements via jQuery:
var myObject = {
foo: $('.foo')
};
myObject.bar = myObject.foo.find('.bar');
myObject.baz = myObject.bar.data('baz');
myObject.foo.css("color", "green");
myObject.bar.css("color", "blue");
$("<div>").html(myObject.baz).appendTo(document.body); // "I'm the data"
<div class="foo">
This is a .foo
<div class="bar" data-baz="I'm the data">
This is a .bar inside the .foo
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Side note: I wouldn't use .data
if all you're doing is accessing data-*
attributes; I'd use attr
instead. data
sets up a data cache for the object, initializes that cache from all data-*
attributes on the object, and then works from the cache, not the attributes. Overkill if you're just reading an attribute value.
Or using my convoluted function:
var myObject = (function() {
var foo, bar;
return {
foo: (foo = $('.foo')),
bar: (bar = foo.find('.bar')),
baz: bar.data('baz')
};
})();
myObject.foo.css("color", "green");
myObject.bar.css("color", "blue");
$("<div>").html(myObject.baz).appendTo(document.body); // "I'm the data"
<div class="foo">
This is a .foo
<div class="bar" data-baz="I'm the data">
This is a .bar inside the .foo
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>