π 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
thisrefers to in each calling context - Diagnose and fix lost-
thisbugs withbind(), 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.
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.
// 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.
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:
- Give the cart an
itemsarray and atotalnumber, both starting empty/zero. - Write
addItem(name, price, quantity = 1)that pushes an item and recomputes the total. - Write a private-feeling
calculateTotal()that usesreduceand setsthis.total. - Write
checkout()that logs each line and the grand total, then empties the cart. - 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;
deleteremoves a key, theinoperator tests for one. - Methods are functions on objects; prefer ES6 shorthand syntax.
thisis decided by how a function is called, not where it is written.- When
thisis lost, fix it withbind(), an arrow callback, or a storedselfβ and never use an arrow for a method that needs the object'sthis.
π 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.