Methods & this
Objects are more than passive bags of data — they can also carry behavior. A method is simply a function stored as a property, and inside that method the this keyword gives you a way to reach back to the object the method was called on. Understanding how this is resolved is one of the most important (and most misunderstood) parts of JavaScript, because it is decided at call time, not where the function is written.
Functions as properties
Any value can live in an object, including a function. When a function is a property of an object, we call it a method.
const user = {
firstName: "Ada",
lastName: "Lovelace",
greet: function () {
return "Hello!";
},
};
console.log(user.greet());
Output:
Hello!
The function is invoked with user.greet(). Because it is reached through user, that object becomes the method’s receiver — and that is exactly what this will point to.
Shorthand method syntax
The classic key: function () {} form is verbose. ES2015 introduced a cleaner method shorthand: drop the colon and the function keyword entirely.
const user = {
firstName: "Ada",
lastName: "Lovelace",
// shorthand method
fullName() {
return `${this.firstName} ${this.lastName}`;
},
};
console.log(user.fullName());
Output:
Ada Lovelace
The shorthand is the modern default. It reads better and is what you will see in nearly all current codebases.
How this works inside methods
Inside a regular (non-arrow) method, this refers to the object the method was called on — the value to the left of the dot at the call site. It is bound dynamically each time the method runs.
const account = {
owner: "Grace",
balance: 100,
deposit(amount) {
this.balance += amount;
return `${this.owner} now has $${this.balance}`;
},
};
console.log(account.deposit(50));
Output:
Grace now has $150
Because this is resolved at call time, the same function can serve different objects:
function describe() {
return `${this.name} (${this.role})`;
}
const dev = { name: "Lin", role: "engineer", describe };
const pm = { name: "Omar", role: "manager", describe };
console.log(dev.describe());
console.log(pm.describe());
Output:
Lin (engineer)
Omar (manager)
Gotcha: If you pull a method off its object and call it bare —
const fn = dev.describe; fn();— there is no object to the left of the dot. In strict mode (and inside ES modules)thisbecomesundefined, sothis.namethrows. Keep the call attached to its receiver, or bind it explicitly.
The arrow-function-as-method pitfall
Arrow functions do not have their own this. They capture this from the surrounding (lexical) scope at the moment they are defined. That makes them a poor choice for top-level methods, because the surrounding scope is usually the module or global, not the object.
const counter = {
count: 0,
// BAD: arrow captures outer this, not `counter`
increment: () => {
this.count++;
return this.count;
},
};
console.log(counter.increment());
Output:
NaN
Here this is not counter, so this.count is undefined, and undefined++ produces NaN. Use a regular method instead:
const counter = {
count: 0,
increment() {
this.count += 1;
return this.count;
},
};
console.log(counter.increment());
console.log(counter.increment());
Output:
1
2
Where arrows do shine is inside a method, for callbacks that should keep the outer this:
const timer = {
label: "build",
steps: ["compile", "bundle", "minify"],
run() {
this.steps.forEach((step) => {
// arrow keeps `this` === timer
console.log(`[${this.label}] ${step}`);
});
},
};
timer.run();
Output:
[build] compile
[build] bundle
[build] minify
If forEach used a regular function (step) {} callback, its this would be undefined and this.label would throw — the arrow neatly avoids that.
Method definitions compared
| Form | Has own this? | this at call | Good for |
|---|---|---|---|
key: function () {} | Yes | The receiver (object before the dot) | Methods (legacy syntax) |
key() {} (shorthand) | Yes | The receiver | Methods (preferred) |
key: () => {} | No | Inherited from enclosing scope | Callbacks inside methods, not top-level methods |
Best Practices
- Use method shorthand (
name() {}) for defining object methods — it is concise and bindsthiscorrectly. - Never use an arrow function as a top-level method when you need
thisto reference the object. - Do use arrow functions for inner callbacks (in
forEach,map,setTimeout) when you want to preserve the surroundingthis. - Remember
thisis determined by how a function is called, not where it is defined. - When passing a method as a callback, preserve its receiver with
obj.method.bind(obj)or wrap it in an arrow:() => obj.method(). - Keep method calls attached to their object; under strict mode a detached call leaves
thisasundefined.