Learn JavaScript

Lesson 1 of 9 · Objects, Destructuring, Spread and JSON

Module 8 · Objects, Destructuring, Spread and JSON

Objects, the Picture: Named Boxes, References and Two Names for One Box

FreeReading

In this lesson

  • Build an object with a literal, and read, add and change its keys with dot and bracket access.
  • Picture a variable as a name that points at an object, so two names can share one box.
  • Explain why const lets the object inside change, and why a spread copy is a new box.

Bob runs the phone list of a cycling club. In Module 7 he kept each member as an array: ["Rafi", "01711", "Dhaka"]. It worked for a week. Then he added an email at index 2, and every line that read the city as member[2] started printing emails. Nobody remembers which number means what.

Bob does not want slots by number. He wants slots by name: the name, the phone, the city. That is exactly what an object is. In this module you learn objects properly. You also learn the one idea that explains most object bugs: a variable does not hold an object, it points at one.

An object is a set of named boxes

An object is one value that holds other values under names. Each name is a key, and a key with its value is a property. You write an object with curly braces. Inside, each property is the key, a colon and the value, with commas between properties. This is an object literal.

const member = { name: "Rafi", phone: "01711", city: "Dhaka" };
console.log(member);
console.log(member.city);
console.log(Object.keys(member).length);
{ name: 'Rafi', phone: '01711', city: 'Dhaka' }
Dhaka
3
Run in Compiler

The first line prints the whole object. The second reads one value by its key. The third counts the keys with Object.keys, which returns the keys as an array. Lesson 02 covers it fully.

A value can be anything: a number, a string, a boolean, an array, even another object. The keys are strings. You may write them without quotes when they are plain words, as above.

Reading a key: dot and brackets

There are two ways to read a property. The dot form, member.city, is short and clear. The bracket form, member["city"], takes a string, so the key can come from a variable or from input.

const member = { name: "Rafi", phone: "01711", city: "Dhaka" };
const field = "phone";
console.log(member.name);
console.log(member["city"]);
console.log(member[field]);
console.log(member.email);
Rafi
Dhaka
01711
undefined
Run in Compiler

The last line asks for a key that does not exist. JavaScript does not stop. It gives undefined, just like an array read past its end. Use the dot when you know the key while writing the code. Use brackets when the key is only known while the program runs.

Adding and changing a key

An object is not fixed. Assigning to a key that does not exist yet adds it. Assigning to a key that exists replaces its value.

const member = { name: "Rafi" };
member.city = "Dhaka";
member["phone"] = "01711";
member.city = "Sylhet";
console.log(member);
console.log(Object.keys(member));
{ name: 'Rafi', city: 'Sylhet', phone: '01711' }
[ 'name', 'city', 'phone' ]
Run in Compiler

Look at the order of the keys. city was added first and changed later, yet it keeps its second place. A changed key stays where it was. A new key goes to the end. Lesson 05 gives the full order rule, which has one surprise for keys that look like numbers.

A name points at an object

Here is the idea that the rest of this module stands on. A variable that "holds" an object really holds a reference: an arrow to where the object lives. The object itself is the box. The variable is a name with an arrow to that box.

Numbers and strings are different. A variable holding 5 holds the value itself. When you copy it, you copy the 5. When you copy a variable that refers to an object, you copy the arrow, not the box.

const rafi = { name: "Rafi", city: "Dhaka" };
const friend = rafi;
friend.city = "Sylhet";
console.log(rafi.city);
console.log(friend === rafi);
Sylhet
true
Run in Compiler

There is only one object here. friend = rafi gave the same arrow a second name. The write through friend lands in the one box, so reading through rafi shows it. === on two objects asks "is this the same box?", and here it is.

const keeps the arrow, not the contents

In Module 1, Maria ran this and was surprised that it worked:

const list = [1, 2];
list.push(3);
console.log(list);
[ 1, 2, 3 ]
Run in Compiler

Now you can answer her. An array is an object, so list holds a reference. const promises that the name list keeps the same arrow forever. It makes no promise about the box. push changes the box, so const has nothing to refuse. What const refuses is list = [9], which would point the name at a new box.

The same holds for every object. const member = {} can gain keys all day. If you want the box itself frozen, Object.freeze does that; Lesson 02 shows it.

A copy is a new box

To get a second, separate object, you must build a new box. The quickest way is spread: { ...rafi } makes a new object and copies every property of rafi into it. Lesson 02 shows the trap inside: the copy is only one level deep.

Watch the five lines in the widget below, one step per line. The names are on the left, the boxes on the right, and an arrow is a reference.

The same steps as a table, one row per step.

StepLineChangedWhat happened
1const rafi = { name: "Rafi", city: "Dhaka" };rafi points at box o1, which holds 2 keys.
2const friend = rafi;friend gets the same arrow: friend === rafi is true.
3friend.city = "Sylhet";o1.cityThe write lands in the one box, so rafi.city is now Sylhet.
4const copy = { ...rafi };Spread builds a new box o2: copy === rafi is false.
5copy.city = "Khulna";o2.cityOnly the new box changes; rafi.city is still Sylhet.

Step 2 is the heart of it: no new box appears, only a second arrow. Step 4 is the opposite: a whole new box, filled with the same values. After step 5 the two boxes hold different cities.

Objects at a glance

const o = { key: value, other: value2 };   // an object literal
o.key          o["key"]       o[name]      // read by dot, by string, by a variable
o.newKey = v;                               // add a key, or replace its value
const p = o;                                // a second name for the same box
const c = { ...o };                         // a new box with the same properties
p === o                                     // true only for the same box
Example 1: a member card from the input

Bob's input is a member's name, phone and city, as three words. This program builds one object from them and prints a card. The keys come from the code; the values come from the input.

const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
const member = { name: tokens[0], phone: tokens[1], city: tokens[2] };
member.joined = 2026;

for (const key of Object.keys(member)) {
  console.log(key + ": " + member[key]);
}
console.log("fields " + Object.keys(member).length);
name: Nadia
phone: 01822
city: Rajshahi
joined: 2026
fields 4

The loop reads every value with brackets, because key is a variable. member.key would look for a key literally named "key". The joined key was added after the literal, so it prints last.

Run in Compiler
Example 2: same contents, different boxes

Two objects can look identical and still be two boxes. This program compares references, then contents.

const a = { city: "Dhaka" };
const b = { city: "Dhaka" };
const c = a;
console.log(a === b);
console.log(a === c);
console.log(a.city === b.city);
c.city = "Comilla";
console.log(a.city, b.city);
false
true
true
Comilla Dhaka

a and b were built by two literals, so they are two boxes, and === says so. c is a second name for a's box. Comparing what is inside means comparing key by key, which Lesson 06 turns into a full deep equality.

Run in Compiler

Where this is used

  • GitHub REST API: every response, such as a repository or a user, is a JSON object with keys like name and id.
  • npm: a project's package.json is one object whose keys, such as name, version and dependencies, Node and npm read directly.
  • Node.js: process.env is an object whose keys are the environment variables, read as process.env.PORT or process.env[name].

Common mistakes

1. A dot where brackets belong

const prices = { tea: 20, coffee: 60 };
const item = "coffee";
console.log(prices.item);
console.log(prices[item]);
undefined
60

prices.item looks for a key literally named "item", which does not exist, so it prints undefined. The dot never reads a variable. When the key is in a variable, use prices[item].

2. A missing comma between properties

const member = { name: "Rafi" city: "Dhaka" };
console.log(member.city);

Node refuses to run the file and says SyntaxError: Unexpected identifier 'city'. After the first value, the parser expects a comma or the closing brace. A space does not separate two properties.

3. Expecting a copy from an assignment

const original = { stock: 10 };
const backup = original;
original.stock = 0;
console.log(backup.stock);
0

The backup was meant to keep 10, but it prints 0. backup = original copied the arrow, so both names point at one box. To keep a separate copy, build a new box: const backup = { ...original };.

Brain teaser

Three names, two boxes

Zara likes to test edges, so she mixes a shared box with a lookalike.

const a = { n: 1 };
const b = a;
const c = { n: 1 };
b.n = 2;
console.log(a.n + c.n);

Does it print 2, 3 or 4? Count the boxes before you count the numbers.

Exercise 1Easy

Amara runs the members' page of a chess club. A member's profile is a set of fields, such as a name, a city and a motto. Each edit sets one field. The first edit of a field adds it, and a later edit of the same field replaces its value. Amara wants the profile after every edit.

Input. The first line holds n. Each of the next n lines is one edit: a field name, one space, then the value. A value can contain spaces.

Output. First, the number of fields in the final profile. Then one line per field, in the order the fields were first added: the field name, a colon, a space and its last value.

Constraints. 1 <= n <= 1000; Each field name is 1 to 8 lowercase letters.; Each value is 1 to 30 letters, digits and spaces, and it never starts or ends with a space..

Sample.

5
name Rafi Hasan
city Sylhet
motto Think twice
city Dhaka
rating 1450
4
name: Rafi Hasan
city: Dhaka
motto: Think twice
rating: 1450
const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];

// your code: read with next() and nextInt(), push every line of output to out

console.log(out.join("\n"));
Run in Compiler

Graded as profile-edits, a free problem in this module's problem set.

Exercise 2Medium

Tanvir runs a small guest house. Each check-in puts a guest in a room, and a later check-in to the same room replaces the earlier guest. At night he asks who is in some rooms.

Input. n, then n check-ins, each a room and a guest's name. Then q, then q rooms to ask about.

Output. one line per question: the guest in that room, or vacant if nobody checked in there.

Constraints. 1 <= n, q <= 1000. Rooms and names are 1 to 10 letters or digits.

Sample. the input below.

4
r101 Nadia
r102 Sumon
r101 Kenji
r205 Lima
3
r101
r103
r205
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];

// your code: read with next() and nextInt(), push every line of output to out

console.log(out.join("\n"));

Sample output.

Kenji
vacant
Lima
Run in Compiler
Exercise 3Hard

Maria wants to check her picture of references. She writes tiny scripts with four commands. new x points the name x at a new empty box. alias y x points y at the box x points at. set x k v writes key k with value v into x's box. get x k prints that key's value, or none.

Input. n, then n commands, one per line, in the form above.

Output. one line per get command.

Constraints. 1 <= n <= 1000. Names, keys and values are 1 to 8 lowercase letters or digits. Every name is created by new or alias before it is used.

Sample. the input below.

7
new a
alias b a
set b city dhaka
new c
set c city khulna
get a city
get c city
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];

// your code: read with next() and nextInt(), push every line of output to out

console.log(out.join("\n"));

Sample output.

dhaka
khulna
Run in Compiler

Common doubts

  • Is an array an object too?

    Yes. An array is an object whose keys are the indexes, plus a special length. That is why const arrays behave like const objects. Module 7's Lesson 05 showed the details.

  • Do I need quotes around keys?

    Not for plain words like name or city. You need them for keys with spaces or dashes, such as { "first name": "Rafi" }. Such a key can only be read with brackets.

  • Why does console.log of an object show it differently from my literal?

    Node prints objects in its own readable format, with spaces after the braces. It is a picture of the object, not the code you wrote. Lesson 02 shows JSON.stringify, which prints a fixed text form.

  • When should I use an object instead of an array?

    Use an array for a list of things of the same kind, in order. Use an object for one thing with named parts, like a member with a name and a city. Lesson 04 makes this a full decision rule.

Key takeaways

  • An object holds values under string keys, written { key: value }.
  • Read with o.key when you know the key, and o[name] when it is in a variable.
  • Assigning a new key adds it at the end; assigning an existing key replaces its value in place.
  • A variable holds a reference to an object; p = o gives the same box a second name.
  • const fixes the reference, not the box; { ...o } builds a new box.
  • Go deeper: Under the Hood, Property Order, Hidden Classes, Inline Caches and the Cost of delete (Pro).

Next: every operation on objects, one by one, with what it returns and whether it changes the object.

End of lesson 1

Mark it done, and your progress moves with you.

Next: Every Object Operation, One by One: Keys, Copies, Destructuring and JSON