This blog post describes five little known facts about ECMAScript 5 (ES5) object literals:
ECMAScript 5 supports getters and setters. You can create them either via property descriptors or via object literals:
var obj = {
get foo() {
return Math.random();
},
set foo(value) {
console.log('SET foo = '+value);
},
};
Let’s use obj
in a REPL:
> obj.foo
0.6029663002118468
> obj.foo
0.99780507478863
> obj.foo = 123;
SET foo = 123
Starting with ES5, it is legal to put a comma after the last property. That helps whenever you want to rearrange properties, especially if there is one property per line:
var translations = {
yes: 'ja',
no: 'nein',
perhaps: 'vielleicht',
};
The grammar rule for PropertyName
in the ES5 specification states that a property name in an object literal is either:
You can use arbitrary text as a property key if you quote it:
var obj = {
'#§$%': true,
" \t\n": true,
};
If the property key is an identifier, it doesn’t have to be quoted in object literals. Identifiers must start with a Unicode letter, an underscore or a dollar sign.
The ES5 spec describes Unicode letters as:
Any character in the Unicode categories “Uppercase letter (Lu)”, “Lowercase letter (Ll)”, “Titlecase letter (Lt)”, “Modifier letter (Lm)”, “Other letter (Lo)”, or “Letter number (Nl)”.
Thus, the following property names don’t have to be quoted:
var obj = {
π: true,
привет: true,
café: true,
$$$: true,
};
You don’t have to quote property names in object literals if they are numeric literals. Numeric literals include the hexadecimal notation, but not a leading minus (-
, a dash), because that is not part of a numeric literal in JavaScript, it is an operator.
var obj = {
1e2: true,
1e-2: true,
.234: true,
0xFF: true,
};
The numbers represented by these numeric literals are converted to strings before they become property keys:
> Object.keys(obj)
[ '100', '255', '0.01', '0.234' ]
In contrast to identifiers, numeric literals cannot be used after the dot operator:
> obj.0xFF
SyntaxError: Unexpected token
> obj[0xFF]
true
My recommendations (for ES5 and later):
For objects holding code, I only use identifiers as property names and never quote.
For objects holding data, I often quote property names.
The JSON data format requires you to quote property names – with double quotes!
> JSON.parse('{ hello: 3 }')
SyntaxError: Unexpected token h
> JSON.parse("{ 'hello': 3 }")
SyntaxError: Unexpected token '
> JSON.parse('{ "hello": 3 }')
{ hello: 3 }
In ECMAScript 3, you had to quote reserved words such as instanceof
or new
if you wanted to use them as property names:
> var obj = { 'new': 123 };
> obj['new']
123
In ECMAScript 5, that is not necessary, anymore:
> var obj = { new: 123 };
> obj.new
123
ECMAScript 5 does not have a built-in data structure for dictionaries (which are also known as maps). Therefore, the programming construct object is a abused as a dictionary from strings to arbitrary values. In addition to keys having to be strings, that causes three problems.
First, You cannot invoke methods on objects-as-dictionaries. The name of any method you invoke might be a key in the data:
> var data = { hasOwnProperty: true };
> data.hasOwnProperty('someKey')
TypeError: boolean is not a function
Second, you must be careful about inheritance. Getting the value of a property and the in
operator include inherited properties, which is not what you want in this case:
> var data = {}; // empty dictionary
> 'toString' in data
true
> data.toString
[Function: toString]
Third, the property key __proto__
triggers special behavior in many engines, which is why you can’t use it as a key for data. One work-around is to escape such keys (and their escaped versions):
function escapeKey(key) {
// Escape '__proto__', its escaped version etc.
if (key.indexOf('__proto__') === 0) {
return key+'%';
} else {
return key;
}
}
function getValue(obj, key) {
return obj[escapeKey(key)];
}
function setValue(obj, key, value) {
obj[escapeKey(key)] = value;
}
An object works best as a dictionary if its prototype is null
(the so-called dict pattern). That fixes problem 2:
> var dict = Object.create(null);
> dict.foo = 123;
> 'toString' in dict
false
> dict.toString
undefined
> 'foo' in dict
true
Problems 1 and 3 remain: you can’t invoke any methods on dict
(it doesn’t inherit any, anyway, because it has no prototypes) and you must escape '__proto__'
.
ECMAScript 6 has the built-in data structure Map
which you should always use for data, especially if it has arbitrary keys.
More information on the dict pattern: section “The dict Pattern: Objects Without Prototypes Are Better Maps” in “Speaking JavaScript”.