Objects
Model data with object literals, shorthand properties, computed keys, in, Object.hasOwn, delete, keys, values and entries.
What you will learn
- Write object literals with shorthand properties, computed keys and methods
- Read and write properties with dot and bracket notation
- Check for a property with in and Object.hasOwn
- List an object's contents with Object.keys, values and entries and rebuild one with fromEntries
On this page
A record groups named fields into one value. The idea is described in the fundamentals lesson on records and objects. In JavaScript the tool is the object: a collection of properties, each a key and a value. This lesson covers the syntax and the built-in functions for inspecting objects. Classes, which build objects from a template, come later. The this lesson came first and used object literals in passing. Here is the full picture.
Object literals
Write an object with braces and key: value pairs. Keys are strings (or symbols). Quotes around a key are needed only when it is not a valid identifier, like "favourite colour".
const user = { name: "Ada", age: 36, "favourite colour": "green", skills: ["maths", "code"],};
console.log(user.name, user["age"]);console.log(user["favourite colour"]);console.log(user.email);
const field = "name";console.log(user[field]);Ada 36greenundefinedAdaAssigning to a key adds or replaces a property, and delete removes one:
user.email = "ada@example.com";user.age = 37;console.log(user);
delete user.email;console.log(user.email, "email" in user);{ name: 'Ada', age: 37, 'favourite colour': 'green', skills: [ 'maths', 'code' ], email: 'ada@example.com'}undefined falseShorthand and computed keys shorten a literal:
const name = "Grace";const age = 85;const shorthand = { name, age };console.log(shorthand);
const key = "level";const dynamic = { [key]: 3, [`${key}Max`]: 10 };console.log(dynamic);{ name: 'Grace', age: 85 }{ level: 3, levelMax: 10 }- Dot notation (
user.name) needs a plain identifier. Bracket notation (user["favourite colour"]) accepts any string, including a key held in a variable (user[field]). - Reading a property that does not exist gives
undefined. It is not an error. - Assigning to a new key adds a property. Assigning to an existing key replaces its value. A
constobject can still gain, lose and change properties. delete user.emailremoves a property. After that,"email" in userisfalse.- Shorthand:
{ name, age }means{ name: name, age: age }. - Computed keys:
[expression]: valueevaluates the expression and uses the result as the key. The example buildslevelandlevelMaxfrom a variable.
Checking for a property
undefined does not tell you whether a property exists, because a property can hold undefined. Use in or Object.hasOwn.
const point = { x: 1, y: 2, label: undefined };
console.log("x" in point, "z" in point);console.log("label" in point, point.label !== undefined);console.log(Object.hasOwn(point, "x"), Object.hasOwn(point, "toString"));console.log("toString" in point);true falsetrue falsetrue falsetrueThe listing functions, a loop over the entries, and fromEntries to build an object back:
console.log(Object.keys(point));console.log(Object.values(point));console.log(Object.entries(point));
for (const [key, value] of Object.entries({ a: 1, b: 2 })) { console.log(key, "=", value);}
const prices = { apple: 1, pear: 2 };const doubled = Object.fromEntries( Object.entries(prices).map(([name, price]) => [name, price * 2]),);console.log(doubled);[ 'x', 'y', 'label' ][ 1, 2, undefined ][ [ 'x', 1 ], [ 'y', 2 ], [ 'label', undefined ] ]a = 1b = 2{ apple: 2, pear: 4 }Whole-number keys are listed first:
const messy = { b: 1, 2: "two", a: 2, 1: "one" };console.log(Object.keys(messy));[ '1', '2', 'b', 'a' ]"x" in pointis true if the object has the property, including inherited ones."toString" in pointistruebecause every object inheritstoString.Object.hasOwn(point, "x")checks only the object’s own properties, soObject.hasOwn(point, "toString")isfalse. This is usually what you want. It replaced the olderobj.hasOwnProperty(key), which fails on objects that do not inherit fromObject.prototypeand can be shadowed by a property of the same name.label: undefinedexists as a property, so"label" in pointis true even though its value isundefined.
Listing the contents
Object.keys, Object.values and Object.entries return arrays of an object’s own enumerable properties. That makes all the array methods available for working on objects. Object.fromEntries goes the other way and builds an object from an array of [key, value] pairs.
The example above shows all four. The loop over Object.entries uses destructuring in the loop head: const [key, value] unpacks each pair. The doubled object is built by mapping over the entries and passing the result to fromEntries.
Key order follows a fixed rule. Keys that look like whole numbers ("1", "2") come first, in ascending order. All other keys follow in the order they were added. That is why messy lists '1', '2', 'b', 'a' although b was written first. If order of insertion matters for arbitrary keys, use a Map, covered later in this module.
Methods and nested data
A function stored in an object is a method. The short form increment() { ... } is the same as increment: function () { ... }. The this rules from the this lesson apply: inside a method called as counter.increment(), this is counter. Returning this allows chaining.
Objects nest. Reading through a missing level fails if you do it with dots, because undefined.host throws a TypeError. The optional chaining operator ?. stops and returns undefined instead, and ?? supplies a fallback, as the null and undefined lesson showed.
const counter = { count: 0, increment() { this.count += 1; return this; }, describe: function () { return `count is ${this.count}`; },};
counter.increment().increment();console.log(counter.describe());count is 2Objects nest, and ?. copes with a missing level:
const config = { server: { port: 8080 } };console.log(config.server.port);console.log(config.database?.host);console.log(config.database?.host ?? "localhost");8080undefinedlocalhostAnd assigning an object copies the reference, not the object:
const original = { a: 1 };const alias = original;alias.a = 2;console.log(original.a, original === alias, { a: 2 } === { a: 2 });2 true falseThe last line is the most important thing to remember about objects. alias and original are two names for one object, so changing one changes the other. Two separate literals that look alike are two objects, and === compares identity, so { a: 2 } === { a: 2 } is false. The references lesson in this module takes this further.
Getters, setters and merging
A property can be computed when you read it. Put get before a method in the literal and it becomes an accessor property: you read it like a plain property, with no brackets after it. Put set before a method and assigning to the name runs it.
const rectangle = { width: 4, height: 3, get area() { return this.width * this.height; }, set size(value) { this.width = value; this.height = value; },};Reading and assigning:
console.log(rectangle.area);rectangle.size = 5;console.log(rectangle.width, rectangle.height, rectangle.area);
try { rectangle.area = 99;} catch (error) { console.log(error.name);}125 5 25TypeErrorrectangle.area looks like a stored value but is calculated each time from width and height, so it is never out of date. Assigning to size runs the setter, which changes both sides. A property with only a getter cannot be assigned to, and in a module that assignment throws a TypeError. Accessors keep the object’s data consistent without asking callers to call methods.
Object.assign(target, ...sources) copies the own properties of each source onto target, left to right, and returns target. Give it an empty object first to get a fresh copy:
const defaults = { theme: "light", size: 12 };const settings = Object.assign({}, defaults, { size: 16 });console.log(settings, defaults);
const target = { a: 1 };const result = Object.assign(target, { b: 2 });console.log(result === target, target);{ theme: 'light', size: 16 } { theme: 'light', size: 12 }true { a: 1, b: 2 }The first call leaves defaults alone because the target is the new {}. The second changes target itself and returns that same object. Later sources win over earlier ones when keys clash. Lesson 27 shows the spread syntax { ...defaults }, which does the same job more concisely. Both make shallow copies.
Common pitfalls
- Using dot notation with a variable.
user.fieldlooks for a property namedfield. Useuser[field]. - Testing existence with
if (obj.key). It is false for0,""andfalseas well as for missing keys. UseObject.hasOwnorin. - Expecting a key order you did not write. Whole-number keys are sorted and come first.
- Using an object as a dictionary with untrusted keys. Inherited names like
toStringappear to exist. AMapavoids this. - Assuming
constfreezes an object. It only fixes the variable. - Reading through a missing level. Use
?.where a level might be absent. - Assigning to a getter-only property. In a module it throws a
TypeError. Add a setter, or do not assign.
Exercises
Create a book object with isbn, title, authors (an array) and available, and a member with a name and a borrowed array. Write lend(book, member) that does nothing and returns false if the book is not available, and otherwise marks it unavailable, adds its ISBN to the member’s list and returns true. Lend the same book twice and print the member and how many properties the book has. The output should be true, false, then the member with one ISBN in borrowed, then 4.
Show solution
const book = { isbn: "978-0-13-468599-1", title: "The Pragmatic Programmer", authors: ["Hunt", "Thomas"], available: true,};
const member = { name: "Ada", borrowed: [],};Then the function, and a program that uses it:
function lend(book, member) { if (!book.available) return false; book.available = false; member.borrowed.push(book.isbn); return true;}
console.log(lend(book, member));console.log(lend(book, member));console.log(member);console.log(Object.keys(book).length);truefalse{ name: 'Ada', borrowed: [ '978-0-13-468599-1' ] }4The second lend returns false because the first one set available to false. The function changes both objects it receives, which is possible because objects are passed by reference. Object.keys(book).length counts the four properties.
Given const stock = { apple: 3, pear: 0, plum: 5 }, print only the names of fruits that are in stock (count above zero), as an array. The output should be [ 'apple', 'plum' ]. Object.keys(stock) gives the names, and stock[name] gives the count for a name.
Show solution
const stock = { apple: 3, pear: 0, plum: 5 };const inStock = Object.keys(stock).filter((name) => stock[name] > 0);console.log(inStock);[ 'apple', 'plum' ]Object.keys returns the list of names. filter keeps a name when stock[name] is above zero, using bracket notation because the name is in a variable. pear has a count of 0, so it is dropped.
Write an object temperature with a celsius property and a fahrenheit accessor. Reading fahrenheit converts from celsius, and assigning to fahrenheit updates celsius. Start at celsius: 100, then print temperature.fahrenheit, assign 32 to fahrenheit and print celsius, then assign 37 to celsius and print fahrenheit. The output should be 212, 0 and 98.6.
Show solution
const temperature = { celsius: 100, get fahrenheit() { return (this.celsius * 9) / 5 + 32; }, set fahrenheit(value) { this.celsius = ((value - 32) * 5) / 9; },};
console.log(temperature.fahrenheit);temperature.fahrenheit = 32;console.log(temperature.celsius);temperature.celsius = 37;console.log(temperature.fahrenheit);212098.6Only celsius is stored. The getter calculates Fahrenheit from it on every read, and the setter converts back, so the two views cannot disagree. Inside both functions this is temperature, because they are called through it.
Summary
- Objects hold named properties. Use dot notation for fixed names and brackets for names held in variables or with unusual characters.
- Shorthand properties, computed keys and method shorthand shorten literals.
inincludes inherited properties.Object.hasOwnchecks only the object’s own.Object.keys,valuesandentriesturn an object into arrays, andfromEntriesturns pairs back into an object.getandsetin a literal make accessor properties that are computed on read and write.Object.assigncopies properties onto a target.- Objects are shared, not copied, when you assign them or pass them to functions.
Next: unpacking objects and arrays with destructuring and spread.