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.

javascript
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]);     // 30

Object 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.

javascript
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); // true

Creation 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.

javascript
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.

javascript
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.

javascript
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 chain

Common 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.

javascript
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.

javascript
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

JavaScript Objects FAQ

What is an object in JavaScript?

A collection of named properties where each property has a string (or Symbol) key and any value. Objects are the most common data structure in JavaScript - arrays, functions, dates, and most other complex values are technically objects. The basic object literal {key: value} is the standard way to group related data and behavior.

What's the difference between dot notation and bracket notation?

Dot notation (obj.property) is cleaner and is the standard for accessing properties with known, valid identifier names. Bracket notation (obj['property']) is for dynamic access when the property name is in a variable, for property names with spaces or special characters, or when the name is computed at runtime. Both produce identical results for simple string keys.

Are JavaScript objects passed by reference or value?

By reference. When you assign an object to a new variable, both variables point to the same object in memory - they're not independent copies. Modifying properties through one variable affects what you see through the other. This is why shallow copy methods (spread operator, Object.assign) are needed when you want an independent object.

How do I copy an object without affecting the original?

Spread operator {... obj} or Object.assign({}, obj) create a shallow copy - only the top-level properties are copied. Nested objects are still shared. For a deep copy of serializable data (no functions or Symbols), use JSON.parse(JSON.stringify(obj)). For objects with functions or circular references, use a library like Lodash's cloneDeep.

What's the difference between Object.create() and object literals?

Object literals create objects whose prototype is Object.prototype, giving them access to standard methods like toString and hasOwnProperty. Object.create(proto) creates an object with proto as its prototype, so the new object inherits from proto's properties. Object.create(null) creates an object with no prototype at all - no inherited properties, which is sometimes useful for plain dictionary-like objects.

How do I check if a property exists?

'key' in obj checks both own and inherited properties. Object.hasOwn(obj, 'key') or obj.hasOwnProperty('key') check only own properties. Using obj.key !== undefined doesn't distinguish between a missing property and a property explicitly set to undefined. For most cases, Object.hasOwn is the clearest modern option.

What are getters and setters?

Special property definitions using get and set keywords that run a function when the property is read or written. Getters let you compute a value on demand - a fullName getter that concatenates firstName and lastName. Setters let you intercept and validate assignments. They look like regular properties to the calling code.

How do I make an object immutable?

Object.freeze() prevents adding, removing, or modifying any property. It's shallow - nested objects are not frozen. Object.seal() prevents adding and removing properties but allows modifying existing ones. For truly deep immutability, you need to recursively freeze nested objects or use a library. Most code uses const (which only prevents reassignment of the variable) rather than freeze.

What does 'this' refer to in object methods?

'this' inside a regular function method refers to the object the method was called on. Inside an arrow function, 'this' is not bound to the calling object - it inherits from the surrounding lexical context, which is usually not what you want for object methods. Use regular function syntax or shorthand methods() for any method that needs to access the object through 'this'.

How do I iterate over object properties?

Object.keys(obj).forEach() iterates over own enumerable string keys. Object.entries(obj).forEach() gives you key-value pairs. for...in also works but includes inherited properties, so pairing it with hasOwnProperty or Object.hasOwn is standard when you don't want prototype properties. For most modern code, Object.entries with destructuring is the cleanest pattern.