JavaScript Objects - Exercise 1

An object in JavaScript is a collection of key-value pairs. The simplest way to create one is with object literal syntax - wrap your pairs in curly braces, separated by commas. Each pair has a key (also called a property name) and a value, joined by a colon. You can also use the Object constructor, but the literal approach is almost always what you'll reach for because it's clean and readable.

javascript

// Object literal - the most common approach
const person = {
  name: 'Alice',
  age: 28,
  city: 'London'
};

// Empty object
const empty = {};

// Using the Object constructor (less common)
const car = new Object();
car.brand = 'Toyota';
car.year = 2022;

console.log(person);       // { name: 'Alice', age: 28, city: 'London' }
console.log(typeof person); // 'object'

There are two ways to read a property from an object: dot notation (obj.key) and bracket notation (obj['key']). Dot notation is cleaner and what you'll use most of the time. Bracket notation is your go-to when the property name is stored in a variable, or when it contains spaces or special characters. If you try to access a property that doesn't exist, you get undefined - no error thrown.

javascript

const book = {
  title: 'JavaScript: The Good Parts',
  author: 'Douglas Crockford',
  pages: 176
};

// Dot notation
console.log(book.title);   // 'JavaScript: The Good Parts'
console.log(book.pages);   // 176

// Bracket notation
console.log(book['author']); // 'Douglas Crockford'

// Using a variable as the key
const key = 'title';
console.log(book[key]);    // 'JavaScript: The Good Parts'

// Property that doesn't exist
console.log(book.price);   // undefined

Adding a new property is as simple as assigning to it - whether it exists or not. If the property is already there, its value gets updated. If it's brand new, JavaScript creates it on the spot. You can use either dot notation or bracket notation to do this. There's no special method needed; assignment is all it takes.

javascript

const user = {
  name: 'Bob',
  age: 30
};

// Add a new property with dot notation
user.email = 'bob@example.com';

// Add a new property with bracket notation
user['isActive'] = true;

console.log(user);
// { name: 'Bob', age: 30, email: 'bob@example.com', isActive: true }

// Update an existing property
user.age = 31;
console.log(user.age); // 31

Use the delete operator to remove a property from an object. After deletion, accessing that property returns undefined. One thing worth knowing: delete only removes own properties - it doesn't touch properties inherited from a prototype. It returns true on success (and also returns true if the property didn't exist at all).

javascript

const product = {
  name: 'Laptop',
  price: 999,
  discount: 0.1
};

console.log(product.discount); // 0.1

delete product.discount;

console.log(product.discount); // undefined
console.log(product);          // { name: 'Laptop', price: 999 }

// delete returns true even if property didn't exist
console.log(delete product.nonExistent); // true

The in operator checks if a property exists on an object (including inherited properties). For checking only the object's own properties - not inherited ones - use Object.hasOwn() (modern) or obj.hasOwnProperty() (older but still widely used). Checking against undefined is unreliable because a property might exist but genuinely hold the value undefined.

javascript

const config = {
  host: 'localhost',
  port: 3000,
  debug: false
};

// 'in' operator
console.log('host' in config);    // true
console.log('timeout' in config); // false

// hasOwnProperty - checks own properties only
console.log(config.hasOwnProperty('port'));    // true
console.log(config.hasOwnProperty('toString')); // false (inherited)

// Object.hasOwn - modern, preferred approach
console.log(Object.hasOwn(config, 'debug')); // true

// Why not check for undefined?
const obj = { score: undefined };
console.log(obj.score === undefined); // true - but the key exists!
console.log('score' in obj);          // true - reliable

The for...in loop iterates over all enumerable properties of an object, including inherited ones. For most cases you'll want to pair it with a hasOwnProperty check to skip prototype properties. Alternatively, Object.keys() gives you just the own keys as an array, which you can then loop with forEach or a regular for loop - often a cleaner approach.

javascript

const scores = {
  Alice: 92,
  Bob: 87,
  Carol: 95
};

// for...in loop
for (const name in scores) {
  console.log(`${name}: ${scores[name]}`);
}
// Alice: 92
// Bob: 87
// Carol: 95

// Using Object.keys() - only own properties
Object.keys(scores).forEach(name => {
  console.log(`${name} scored ${scores[name]}`);
});

Both notations do the same thing - read or write a property - but they have different use cases. Dot notation is shorter and more readable, so use it when you know the property name upfront and it's a valid identifier. Bracket notation is more flexible: use it when the property name is dynamic (stored in a variable), contains special characters, starts with a number, or is a reserved keyword.

javascript

const data = {
  name: 'Carol',
  'first-name': 'Carol',   // hyphen - only bracket notation works
  1stItem: 'apple'          // starts with number - bracket notation only
};

// Dot notation - clean and simple
console.log(data.name);          // 'Carol'

// Bracket notation - needed for special keys
console.log(data['first-name']); // 'Carol'

// Dynamic key (stored in variable) - bracket notation required
const prop = 'name';
console.log(data[prop]);         // 'Carol'

// This would be a syntax error:
// console.log(data.first-name);

Object.keys() returns an array of an object's own enumerable property names (the keys). It only looks at the object itself, not anything inherited through the prototype chain. This is really handy when you want to count properties, iterate cleanly, or check what fields an object has.

javascript

const settings = {
  theme: 'dark',
  language: 'en',
  fontSize: 14,
  notifications: true
};

const keys = Object.keys(settings);
console.log(keys);        // ['theme', 'language', 'fontSize', 'notifications']
console.log(keys.length); // 4

// Count how many properties an object has
console.log(Object.keys(settings).length); // 4

// Loop using the keys
Object.keys(settings).forEach(key => {
  console.log(`${key} = ${settings[key]}`);
});

Object.values() gives you an array of an object's own enumerable property values, in the same order as Object.keys(). It pairs nicely with array methods like reduce or filter when you want to work with the data without caring about the keys. It was introduced in ES2017, so it's fully supported everywhere you'd write modern JavaScript.

javascript

const prices = {
  apple: 1.2,
  banana: 0.5,
  cherry: 3.0
};

const values = Object.values(prices);
console.log(values); // [1.2, 0.5, 3]

// Sum all prices
const total = Object.values(prices).reduce((sum, price) => sum + price, 0);
console.log(total); // 4.7

// Find the most expensive item value
const max = Math.max(...Object.values(prices));
console.log(max); // 3

A shallow copy creates a new object with the same top-level properties. The two most common ways are the spread operator ({ ...obj }) and Object.assign({}, obj). Both do the same thing: copy the own enumerable properties into a fresh object. The catch is that nested objects are still shared by reference - changing a nested property in the copy will affect the original too. That's what makes it "shallow".

javascript

const original = { name: 'Dave', age: 25, city: 'Paris' };

// Using spread - modern and concise
const copy1 = { ...original };
copy1.name = 'Eve';

console.log(original.name); // 'Dave' - untouched
console.log(copy1.name);    // 'Eve'

// Using Object.assign
const copy2 = Object.assign({}, original);
copy2.age = 99;

console.log(original.age); // 25 - untouched
console.log(copy2.age);    // 99

// Watch out: nested objects are still shared
const obj = { name: 'Frank', address: { city: 'Rome' } };
const shallowCopy = { ...obj };
shallowCopy.address.city = 'Milan';

console.log(obj.address.city); // 'Milan' - both point to the same nested object!