JavaScript Object Data Type
Object Basics
Objects are the most commonly used data structure in JavaScript. An object is a collection of named values - properties - where each property has a key and a value. Keys are strings (or Symbols), and values can be anything: strings, numbers, booleans, arrays, other objects, or functions. Unlike the primitive types (string, number, boolean), objects are reference types - a variable that holds an object holds a reference to it in memory, not a copy of its contents. This affects how they behave when assigned to other variables or passed to functions.
1// Object literals - the most common creation method
2const person = {
3 name: "Alice",
4 age: 30
5};
6
7const car = {
8 brand: "Toyota",
9 model: "Camry",
10 year: 2022
11};
12
13// Dot notation and bracket notation for property access
14console.log(person.name); // "Alice"
15console.log(car["model"]); // "Camry"
16
17// Bracket notation for dynamic property names
18const key = "age";
19console.log(person[key]); // 30Object Characteristics
Key-value pairs- Data stored as named properties. Keys are strings or Symbols, values can be any type.Reference type- Variables hold a reference to the object in memory, not a copy. Assigning an object to another variable gives you two references to the same object.Mutable- Properties can be added, modified, and deleted after creation.Dynamic- Properties can be created and changed at runtime, including computed property names.
Object Creation Methods
The object literal syntax - curly braces with key-value pairs - is the standard and most readable way to create objects in almost every situation. The new Object() constructor produces the same result with more typing. Object.create() is useful when you need to set the prototype explicitly. ES6 classes provide the syntax for object-oriented patterns when you need multiple instances of the same shape. Computed property names with square bracket syntax let you use expressions as keys, which is useful when building objects dynamically.
1// Object literal - use this by default
2const person = {
3 firstName: "John",
4 lastName: "Doe",
5 age: 30
6};
7
8// new Object() - same result, not commonly used
9const car = new Object();
10car.brand = "Honda";
11car.model = "Civic";
12
13// Object.create() - sets prototype explicitly
14const animal = { type: "mammal" };
15const dog = Object.create(animal);
16dog.name = "Buddy";
17console.log(dog.type); // "mammal" - from prototype
18
19// Class syntax - for multiple instances of same shape
20class User {
21 constructor(name, age) {
22 this.name = name;
23 this.age = age;
24 }
25}
26const alice = new User("Alice", 25);
27
28// Computed property names
29const key = "score";
30const game = {
31 player: "Bob",
32 [key]: 95, // key variable evaluates to "score"
33 ["level" + "3"]: true // expression evaluates to "level3"
34};
35console.log(game.score); // 95
36console.log(game.level3); // trueCreation Methods
Object literal {}- The standard approach. Clean, readable, used in the vast majority of code.new Object()- Produces the same result as {} but more verbose. Rarely used.Object.create(proto)- Creates an object with a specified prototype. Useful for prototype-based inheritance.Class syntax- ES6 classes for creating many objects of the same structure.
Object Properties and Methods
Properties hold data; methods are properties whose value is a function. Both use the same syntax. Dot notation is the standard for accessing known property names. Bracket notation is for dynamic access - when the property name is stored in a variable or computed at runtime. One gotcha worth knowing: arrow functions as object methods don't have their own 'this' - they inherit it from the surrounding context, which is usually not the object itself. Use regular function syntax or the shorthand method syntax for methods that need to reference the object.
1const person = {
2 firstName: "Alice",
3 lastName: "Smith",
4 age: 30,
5
6 // Regular function method - 'this' works correctly
7 getFullName: function() {
8 return this.firstName + " " + this.lastName;
9 },
10
11 // Shorthand method syntax (ES6) - also works
12 celebrateBirthday() {
13 this.age++;
14 return `Happy birthday! Now ${this.age}.`;
15 },
16
17 // Arrow function - 'this' does NOT refer to person
18 // badMethod: () => this.firstName // undefined
19};
20
21// Dot notation
22console.log(person.firstName); // "Alice"
23console.log(person.getFullName()); // "Alice Smith"
24
25// Bracket notation for dynamic access
26const prop = "age";
27console.log(person[prop]); // 30
28
29// Nested objects
30const company = {
31 name: "Tech Corp",
32 address: {
33 street: "123 Main St",
34 city: "Boston"
35 },
36 employees: ["Alice", "Bob"]
37};
38
39console.log(company.address.city); // "Boston"
40console.log(company.employees[0]); // "Alice"Property Access
Dot notation- obj.property - use for known, valid identifier property names.Bracket notation- obj[key] - use when the property name is dynamic, computed, or contains special characters.Method definition- Use regular function syntax or shorthand methods() when the function needs to reference 'this'.Nested access- Chain dot or bracket notation to access nested properties.
Modifying Objects
Objects are mutable - you can add new properties, update existing ones, and delete properties at any time. The delete operator removes a property entirely. The in operator checks whether a property exists anywhere on the object including its prototype chain. hasOwnProperty checks only properties the object itself owns. Object.keys, Object.values, and Object.entries return arrays of property names, values, and pairs respectively - and all three only include own enumerable properties.
1const person = { name: "John", age: 25 };
2
3// Adding properties
4person.email = "john@example.com";
5person["phone"] = "555-1234";
6
7// Modifying
8person.age = 26;
9person.name = "John Doe";
10
11// Deleting
12delete person.phone;
13
14// Checking existence
15console.log("name" in person); // true - own or inherited
16console.log(person.hasOwnProperty("email")); // true - own only
17
18// Getting property lists
19console.log(Object.keys(person)); // ["name", "age", "email"]
20console.log(Object.values(person)); // ["John Doe", 26, "john@example.com"]
21console.log(Object.entries(person)); // [["name", "John Doe"], ["age", 26], ...]
22
23// Iterating
24for (const key in person) {
25 if (person.hasOwnProperty(key)) { // skip inherited
26 console.log(`${key}: ${person[key]}`);
27 }
28}
29
30// Shallow copy
31const copy1 = Object.assign({}, person);
32const copy2 = { ...person }; // spread - same result, cleaner
33
34// Merging
35const defaults = { theme: "light", language: "en" };
36const user = { theme: "dark" };
37const merged = { ...defaults, ...user }; // later properties win
38console.log(merged); // { theme: "dark", language: "en" }Built-in Object Methods
The Object constructor itself comes with a collection of static methods that handle common tasks. Object.freeze makes an object immutable - properties can't be added, removed, or changed. Object.seal is softer - it prevents adding and removing properties but allows modifying existing ones. Object.defineProperty gives you fine-grained control over a property's configurability, writability, and enumerability. Object.create with a null prototype is useful when you want an object with no prototype at all, avoiding any inherited properties.
1const person = { name: "Alice", age: 30, role: "developer" };
2
3// Keys, values, entries
4console.log(Object.keys(person)); // ["name", "age", "role"]
5console.log(Object.values(person)); // ["Alice", 30, "developer"]
6console.log(Object.entries(person)); // [["name", "Alice"], ["age", 30], ...]
7
8// Object.assign - merge into target
9const extended = Object.assign({}, person, { city: "Boston" });
10console.log(extended);
11// { name: "Alice", age: 30, role: "developer", city: "Boston" }
12
13// Object.freeze - full immutability
14const frozen = Object.freeze({ data: "cannot change" });
15// frozen.data = "new"; // Silently fails (throws in strict mode)
16console.log(Object.isFrozen(frozen)); // true
17
18// Object.seal - prevent add/remove, allow modify
19const sealed = Object.seal({ count: 0 });
20sealed.count = 10; // Allowed
21// sealed.extra = 1; // Silently fails
22console.log(Object.isSealed(sealed)); // true
23
24// Object.hasOwn - modern alternative to hasOwnProperty
25console.log(Object.hasOwn(person, "name")); // true
26console.log(Object.hasOwn(person, "toString")); // false - inherited
27
28// Object.create with null prototype - no inherited properties
29const clean = Object.create(null);
30clean.key = "value";
31// clean.toString is undefined - no prototype chainCommon Object Patterns
A few patterns come up constantly in JavaScript code. Configuration objects - passing options to a function as a named-property object - is cleaner than long argument lists because you can omit optional ones and their names document themselves. Factory functions return a new object every call, which is often simpler than classes for straightforward object creation. Object destructuring pulls specific properties into local variables cleanly. The options-with-defaults pattern combines destructuring and default values to give functions clean fallbacks.
1// Configuration object - named params, order doesn't matter
2const appConfig = {
3 apiUrl: "https://api.example.com",
4 timeout: 5000,
5 retries: 3,
6 features: { auth: true, analytics: false }
7};
8
9// Factory function
10function createUser(name, email, age) {
11 return {
12 name, // shorthand for name: name
13 email,
14 age,
15 isActive: true,
16 getProfile() {
17 return `${this.name} (${this.email})`;
18 }
19 };
20}
21
22const u1 = createUser("Alice", "alice@ex.com", 30);
23const u2 = createUser("Bob", "bob@ex.com", 25);
24console.log(u1.getProfile()); // "Alice (alice@ex.com)"
25
26// Destructuring
27const { name, age, email } = u1;
28console.log(name, age); // "Alice" 30
29
30// Rename while destructuring
31const { name: userName, email: userEmail } = u1;
32console.log(userName); // "Alice"
33
34// Options with defaults
35function connect({ host = "localhost", port = 3000, debug = false } = {}) {
36 console.log(`Connecting to ${host}:${port}, debug: ${debug}`);
37}
38
39connect({ port: 8080 }); // host defaults to localhost
40connect(); // all defaults
41
42// Namespace pattern - avoid global pollution
43const MyApp = {
44 config: { version: "1.0.0", env: "production" },
45 utils: {
46 formatDate(d) { return d.toISOString(); }
47 }
48};Object Best Practices
Use const for object variables - it prevents accidentally reassigning the whole object while still allowing property changes, which is usually the right constraint. Use the shorthand property syntax when a variable name matches the property name you want. Use method shorthand syntax for functions inside objects that use 'this'. For shallow copying and merging, the spread operator is cleaner than Object.assign. For deep copies of nested objects, the standard JSON roundtrip (JSON.stringify then parse) works for serializable data but drops functions, undefined, and Symbol values.
1// const prevents accidental reassignment
2const config = { apiKey: "abc123", maxRetries: 3 };
3// config = {}; // TypeError
4config.apiKey = "def456"; // Fine - modifying property
5
6// Shorthand properties
7const name = "Alice";
8const age = 30;
9const person = { name, age }; // { name: "Alice", age: 30 }
10// vs { name: name, age: age }; // redundant
11
12// Method shorthand
13const calc = {
14 add(a, b) { return a + b; }, // shorthand
15 multiply(a, b) { return a * b; } // shorthand
16};
17
18// Avoid arrow functions as object methods
19const obj = {
20 value: 42,
21 // badMethod: () => this.value, // undefined - no own 'this'
22 goodMethod() { return this.value; } // 42
23};
24
25// Spread for copying and merging
26const original = { a: 1, b: 2 };
27const copy = { ...original }; // shallow copy
28const extended = { ...original, c: 3 }; // copy + new property
29
30// Deep copy for nested objects (serializable data only)
31const nested = { user: { name: "John", prefs: { theme: "dark" } } };
32const deepCopy = JSON.parse(JSON.stringify(nested));
33deepCopy.user.prefs.theme = "light";
34console.log(nested.user.prefs.theme); // "dark" - original unchanged
35
36// Property existence check options
37const data = { value: 0 };
38console.log("value" in data); // true - own or inherited
39console.log(Object.hasOwn(data, "value")); // true - own only (modern)
40console.log(data.value !== undefined); // true - but misses explicit undefined