Skip to main content

πŸ”‘ Properties, Methods, and the this Keyword

If objects are the nouns of JavaScript, properties are their adjectives and methods are their verbs. This lesson digs into both β€” then tackles this, the single most misunderstood keyword in the language, and shows you exactly why it slips and how to pin it down.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Store and access properties of every value type, including nested objects and arrays
  • Add, modify, and remove properties, and test for their existence
  • Write methods using shorthand syntax and call methods from other methods
  • Explain what this refers to in each calling context
  • Diagnose and fix lost-this bugs with bind(), arrow functions, and stored references

Estimated Time: 40–50 minutes  β€’  Difficulty: Intermediate

Hands-on: Build a self-contained shopping-cart object whose methods use this to manage state.

In This Lesson

Properties in Depth

A property is a named value on an object. If properties are what an object is, methods are what it can do. Think of a house: its properties are its color, its number of rooms, its square footage β€” the facts that describe it.

An object and its properties A house object with three properties: color blue, rooms four, and square footage two thousand. Object: house color: "blue" rooms: 4 sqft: 2000
Figure 1 β€” Properties are the named facts stored on an object.

A property can hold any value: a primitive (string, number, boolean, null), a complex value (array or nested object), or a function (which we then call a method).

const person = {
  // Primitive values
  name: 'Sarah',        // string
  age: 32,              // number
  isEmployed: true,     // boolean
  favoriteColor: null,  // null

  // Complex values
  hobbies: ['reading', 'hiking', 'cooking'],  // array
  address: {                                  // nested object
    street: '123 Main St',
    city: 'Springfield',
    zipCode: '12345',
  },

  // Function β†’ method
  greet() {
    return `Hello, my name is ${this.name}`;
  },
};

Accessing & Modifying Properties

Two notations reach a property. Dot notation is concise and reads naturally for names you know. Bracket notation is required when the name is in a variable, or when the name contains spaces or dashes.

graph LR A[Property Access] --> B[Dot Notation] A --> C[Bracket Notation] B -->|"person.name"| D[Known valid name] C -->|"person['name']"| E[String key] C -->|"person[variable]"| F[Dynamic key]
// Dot notation
console.log(person.name);         // 'Sarah'
console.log(person.address.city); // 'Springfield'

// Bracket notation β€” dynamic key from a variable
const field = 'hobbies';
console.log(person[field]);       // ['reading', 'hiking', 'cooking']

// Names with spaces or dashes REQUIRE brackets
const product = {
  'product id': 'A12345',
  'shipping-info': { carrier: 'FedEx', method: 'Ground' },
};
console.log(product['product id']);            // 'A12345'
console.log(product['shipping-info'].carrier); // 'FedEx'

Objects are dynamic β€” you can add, change, and remove properties at any time.

const car = { make: 'Honda', model: 'Civic', year: 2020 };

// Add
car.color = 'blue';
car['fuelType'] = 'gasoline';

// Modify
car.year = 2021;

// Remove β€” delete drops the property entirely
delete car.color;

// Test existence with the `in` operator
console.log('color' in car);    // false
console.log('fuelType' in car); // true

⚠️ delete vs. setting to undefined

Setting car.fuelType = undefined keeps the key on the object (it still shows up in Object.keys() and the in operator) but empties its value. delete car.fuelType removes the key altogether. Reach for delete when you truly want the property gone.

Object Methods

A method is a function stored as a property. Methods give an object behavior. A calculator's properties might be its brand and model; its methods are the operations it performs.

const calculator = {
  // Function-expression method (older style)
  add: function (a, b) {
    return a + b;
  },

  // Shorthand method (ES6, preferred)
  subtract(a, b) {
    return a - b;
  },

  // A method that calls other methods via `this`
  calculate(operation, a, b) {
    switch (operation) {
      case 'add': return this.add(a, b);
      case 'subtract': return this.subtract(a, b);
      default: return 'Unknown operation';
    }
  },
};

console.log(calculator.add(5, 3));                 // 8
console.log(calculator.calculate('subtract', 10, 4)); // 6

πŸ’‘ Prefer shorthand method syntax

Modern JavaScript favors subtract(a, b) { … } over subtract: function (a, b) { … }. It is shorter, and it clearly signals "this is a method." One caveat: do not use an arrow function for a method that needs this β€” you will see why in the arrow-functions section.

The this Keyword

this is a reference that a function receives automatically when it runs. Its value is not fixed by where you write the function β€” it is decided by how the function is called. Think of this as the word "me": who "me" refers to depends entirely on who is speaking.

graph TD A["What does 'this' point to?"] --> B[Object method call] A --> C[Constructor with new] A --> D[Standalone function] A --> E[Arrow function] B --> B1["The object left of the dot"] C --> C1["The newly created object"] D --> D1["undefined in strict mode"] E --> E1["Inherited from the enclosing scope"]

The everyday case is a method call: when you call object.method(), this inside the method is that object β€” whatever sits to the left of the dot.

const user = {
  firstName: 'John',
  lastName: 'Doe',
  fullName() {
    return `${this.firstName} ${this.lastName}`; // `this` is user
  },
  rename(newFirst) {
    this.firstName = newFirst; // mutates user.firstName
  },
};

console.log(user.fullName()); // 'John Doe'
user.rename('Jane');
console.log(user.fullName()); // 'Jane Doe'

This is what makes objects self-contained: a method can read and update its own object's properties through this, without knowing the object's variable name.

The Lost-this Problem

Because this is set by the call site, pulling a method off its object breaks the connection. The method still exists, but there is no object to the left of the dot anymore.

const user = {
  name: 'Alice',
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

user.greet(); // 'Hello, my name is Alice' β€” called on user

// Detach the method into a plain variable…
const greet = user.greet;
greet();      // 'Hello, my name is undefined' β€” no object, no `this`

Console output

Hello, my name is Alice
Hello, my name is undefined

This bites most often when you pass a method as a callback β€” to setTimeout, an event listener, or an array method. The callback is invoked later as a bare function, so its this is no longer your object.

Three Fixes for this

1. bind() β€” lock this permanently

bind() returns a new copy of the function with this nailed to the object you pass, no matter how it is later called.

const boundGreet = user.greet.bind(user);
boundGreet(); // 'Hello, my name is Alice' β€” bound for good

2. Arrow functions β€” inherit this from the surrounding scope

An arrow function has no this of its own; it borrows the this of the code around it. That makes arrows perfect for callbacks inside a regular method.

const user2 = {
  name: 'Bob',
  delayedGreet() {
    // Regular method β†’ `this` is user2.
    // The arrow callback keeps that same `this`.
    setTimeout(() => {
      console.log(`Hello, my name is ${this.name}`);
    }, 1000);
  },
};
user2.delayedGreet(); // after 1s: 'Hello, my name is Bob'

3. Store this in a variable (the classic pattern)

Before arrow functions, developers captured this into a variable β€” often named self or that β€” so an inner function could still reach it.

const user3 = {
  name: 'Charlie',
  delayedGreet() {
    const self = this; // capture before entering the callback
    setTimeout(function () {
      console.log(`Hello, my name is ${self.name}`);
    }, 1000);
  },
};
user3.delayedGreet(); // after 1s: 'Hello, my name is Charlie'

βœ… Which fix to choose

For callbacks inside a method, the arrow function is cleanest and most modern. Use bind() when you must hand a detached method to code you don't control (like a DOM event listener). The self variable is legacy β€” recognize it in old code, but prefer arrows in new code.

Arrow Functions & this: A Double-Edged Sword

Arrow functions inheriting this is exactly what you want for callbacks β€” and exactly what you do not want for the method itself. An arrow method has no object this; it grabs the outer scope's this (often the module or global), so it cannot see the object's own properties.

const counter = {
  count: 0,

  // Regular method β†’ `this` is counter. Correct.
  incrementRegular() {
    this.count++;
    return this.count;
  },

  // Arrow method β†’ `this` is NOT counter. Broken.
  incrementArrow: () => {
    this.count++;      // `this` is the outer scope, not counter
    return this.count; // NaN
  },
};

console.log(counter.incrementRegular()); // 1
console.log(counter.incrementArrow());   // NaN

⚠️ Rule of thumb

Don't use an arrow function for a method that needs this to be the object. Do use an arrow function for a callback inside a regular method, precisely because it keeps the method's this.

Here is the good use of an arrow β€” a callback that correctly keeps the object's this:

const inventory = {
  products: ['Laptop', 'Phone', 'Tablet'],
  store: 'Electronics Store',
  showProducts() {
    // Arrow callback keeps `this` = inventory
    this.products.forEach((product) => {
      console.log(`${product} is in stock at ${this.store}`);
    });
  },
};

inventory.showProducts();
// Laptop is in stock at Electronics Store
// Phone is in stock at Electronics Store
// Tablet is in stock at Electronics Store

Hands-on Exercise

πŸ‹οΈ Build a Self-Contained Shopping Cart

Objective: Create a shoppingCart object whose methods use this to manage the cart's own state.

Instructions:

  1. Give the cart an items array and a total number, both starting empty/zero.
  2. Write addItem(name, price, quantity = 1) that pushes an item and recomputes the total.
  3. Write a private-feeling calculateTotal() that uses reduce and sets this.total.
  4. Write checkout() that logs each line and the grand total, then empties the cart.
  5. Make sure every method reads and writes cart state through this.
πŸ’‘ Hint

Inside calculateTotal, the reduce callback should be an arrow function so it keeps the cart's this. After checkout logs everything, reset this.items = [] and this.total = 0.

βœ… Sample solution
const shoppingCart = {
  items: [],
  total: 0,

  addItem(name, price, quantity = 1) {
    this.items.push({ id: Date.now().toString(), name, price, quantity });
    this.calculateTotal();
    console.log(`Added ${quantity} Γ— ${name}`);
  },

  calculateTotal() {
    this.total = this.items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0,
    );
  },

  checkout() {
    if (this.items.length === 0) {
      console.log('Your cart is empty');
      return;
    }
    console.log('=== Order Summary ===');
    this.items.forEach((item) => {
      console.log(`${item.name} Γ—${item.quantity}: $${(item.price * item.quantity).toFixed(2)}`);
    });
    console.log(`Total: $${this.total.toFixed(2)}`);
    this.items = [];
    this.total = 0;
    console.log('Thank you for your purchase!');
  },
};

shoppingCart.addItem('Laptop', 999.99);
shoppingCart.addItem('Mouse', 29.99, 2);
shoppingCart.checkout();

🎯 Quick Quiz

Question 1: Inside a normal method called as obj.method(), what does this refer to?

Question 2: Why does const g = user.greet; g(); log undefined for the name?

Question 3: Which is the best use of an arrow function?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Properties hold any value β€” primitives, arrays, nested objects, or functions.
  • Use dot notation for known names, bracket notation for dynamic or unusual names; delete removes a key, the in operator tests for one.
  • Methods are functions on objects; prefer ES6 shorthand syntax.
  • this is decided by how a function is called, not where it is written.
  • When this is lost, fix it with bind(), an arrow callback, or a stored self β€” and never use an arrow for a method that needs the object's this.

πŸ“š Further Reading

πŸš€ What's Next?

You now understand how a single object holds state and behavior. Next we follow the trail this leaves behind into the prototype chain β€” how objects inherit properties and methods from one another, the engine beneath both Object.create() and ES6 classes.

πŸŽ‰ Great work!

You've tamed this β€” the concept that trips up most JavaScript learners for months.