JAVASCRIPT / OBJECTS AND PROPERTY MECHANICS
Object literals and property shorthand
Build objects with literal syntax and use property and method shorthand deliberately, knowing which key each form creates and what the value is bound to.
What you will learn
- Write { x } instead of { x: x } and explain why it cannot rename the key
- Use method shorthand and know it yields a function that cannot be called with new
- Pick between identifier, quoted, and numeric keys, all of which become strings
- Wrap a returned literal in parentheses so { is not parsed as a block
Understanding Object literals and property shorthand
An object literal is an expression, not a declaration: each time the engine evaluates `{ ... }` it allocates a brand new object and installs the listed properties in the order written. The key half of every entry is fixed while the code is parsed, and it can be written three ways - a bare identifier like `id`, a quoted string like `'data-id'`, or a numeric literal like `1.50` - all of which end up as string keys, since ordinary objects only store strings and symbols. The value half is a full expression evaluated at that moment, so a literal inside a loop or a function produces a fresh, independent object per iteration or per call.
Property shorthand exists for the very common case where the key you want and the binding holding the value are spelled the same: `{ id }` is defined to mean exactly `{ id: id }`. The left half of that expansion is the identifier's text and the right half is a single read of that binding, which explains the two things people trip on. You cannot rename with shorthand, so if the incoming variable is `id` but the key must be `userId` you have to write `{ userId: id }`; and the property does not follow the variable afterwards, because the read already happened and the object now holds a copy of that value.
Method shorthand, `summary() { ... }`, looks like a pure abbreviation of `summary: function () { ... }` but differs in substance. A shorthand method is not a constructor: it has no `prototype` property and `new obj.summary()` throws, which is what you want for something only ever called as a method. It also remembers the object it was written in, so `super.summary()` can resolve inside it, while a plain function expression in the same slot cannot use `super`. Naming is not a reason to choose between them: an anonymous function expression assigned to a key picks up that key as its `name` too.
const id = 7;
const label = 'sensor';
const readings = [21.5, 22.1];
const device = {
id,
label,
readings,
summary() {
return `${this.label}#${this.id}: ${this.readings.length} readings`;
},
toText: function () {
return this.summary();
},
};
console.log(Object.keys(device).join(','));
console.log(device.summary());
console.log(device.toText());
console.log(device.summary.name, device.toText.name);
console.log('prototype' in device.summary, 'prototype' in device.toText);Shorthand is abbreviation, not linkage: `{ x }` means `{ x: x }`, so the key is the identifier's spelling and the value is a one-time copy of what that binding held when the literal ran.
Worked examples
Shorthand takes the name, not just the value
Shows that the key comes from the identifier's spelling, and what happens when you need a different key or a computed value.
const width = 10;
const height = 4;
const size = { width, height };
console.log(JSON.stringify(size));
const renamed = { w: width, h: height };
console.log(JSON.stringify(renamed));
const clash = { width, width: 20 };
console.log(clash.width);
const logical = { width, isWide: width > height };
console.log(JSON.stringify(logical));Example explained
Line 1`{ width, height }` copies both key names from the identifier text, so the keys are exactly `width` and `height`.
Line 2`{ w: width, h: height }` is the only way to store a value under a different key; shorthand has no rename form.
Line 3`{ width, width: 20 }` defines the same key twice and the later entry wins, with no error at all.
Line 4Shorthand accepts only a bare identifier, so anything computed such as `width > height` needs an explicit key.
A literal after => needs parentheses
Demonstrates why an arrow body that starts with a brace is read as a block, and that each evaluation of a literal makes a new object.
const makeUser = (name, age) => ({ name, age });
const oops = (name, age) => { name, age };
console.log(JSON.stringify(makeUser('ada', 36)));
console.log(oops('ada', 36));
const users = ['ada', 'linus'].map((name, index) => ({ name, index }));
console.log(JSON.stringify(users));Example explained
Line 1In `makeUser` the parentheses force the parser into expression position, so `{ name, age }` is an object literal.
Line 2In `oops` the body is a block whose single statement is the comma expression `name, age`; nothing is returned, so the call yields `undefined` without any error.
Line 3A statement that begins with `{` is read as a block for the same reason, which is why literals get wrapped there too.
Line 4The literal inside `map` is evaluated once per element, so the two array entries are separate objects.
The three ways to write a key
Shows that reserved words are legal keys, when quotes are required, and how numeric keys are converted and ordered.
const config = {
class: 'primary',
'data-id': 42,
1.50: 'one and a half',
0b11: 'three',
};
console.log(config.class, config['data-id']);
console.log(Object.keys(config).join('|'));
console.log(config[1.5] === config['1.5']);
console.log(config[3]);Example explained
Line 1`class:` is a fine key because reserved words are restricted as variable names, not as property names in a literal.
Line 2`'data-id'` must be quoted since a hyphen cannot appear in an identifier, and reading it then requires brackets.
Line 3Numeric keys are converted to strings, so `1.50` becomes `1.5` and `0b11` becomes `3`; `config[1.5]` and `config['1.5']` hit the same property.
Line 4`Object.keys` reports integer-like keys first in numeric order and the remaining keys in insertion order, which is why `3` comes before `class`.
Important notes
Inside a literal, `__proto__: value` (quoted or not) is special-cased and sets the new object's prototype instead of creating an own property; the shorthand `{ __proto__ }` and method `__proto__() {}` forms are not special and do create ordinary properties.
An arrow function as a property value has no `this` of its own, so `label: () => this.name` reads `this` from the enclosing scope; use method shorthand when `this` must be the object.
Common mistakes
Treating `{ count }` as a live link to the variable: after `count = 99` the property still holds the original number, so later reads silently use stale data.
Writing a factory as `(name) => { name }`: the braces parse as a function body, the call returns `undefined`, and the failure surfaces much later as "Cannot read properties of undefined".
Repeating a key, as in `{ radius, radius: radius * 2 }`: nothing is reported, the last entry silently wins, and the earlier one is discarded.
Try it yourself
Change, predict, then run
In the browser console, run `let width = 3, height = 4` and then `const rect = { width, height, area() { return this.width * this.height; } }`, set `width = 100`, and log `rect.width` and `rect.area()` to confirm the literal copied the value once. Then evaluate `new rect.area()` and read the TypeError to see that shorthand methods are not constructors.
Open the JavaScript workspaceCheck your understanding
Given `let total = 1; const cart = { total, sum() { return total; } }; total = 99;`, what do `cart.total` and `cart.sum()` produce?
- cart.total is 1 and cart.sum() is 99
- cart.total is 99 and cart.sum() is 99
- cart.total is 1 and cart.sum() is 1
- cart.total is 99 and cart.sum() is 1
Show answer
The shorthand entry read `total` once while the literal was being evaluated and stored the number 1 in a new property, so reassigning the variable cannot change it. The method body reads the variable `total` through its closure on every call, so it sees 99. Option 2 assumes shorthand creates a live alias to the binding; properties hold values, not bindings.