How to Use Object Methods and the "this" Keyword in JavaScript
Objects in JavaScript are not just containers for data. They can also contain functions that operate on that data. When a function is stored as a property of an object, it is called a method. Methods give objects behavior, allowing them to act on their own properties.
At the heart of object methods lies the this keyword, one of the most powerful and simultaneously most confusing features in JavaScript. Unlike many other programming languages where this always refers to the instance that owns the method, JavaScript determines this at the moment the function is called, not when it is defined. This dynamic behavior is the source of countless bugs for developers who do not fully understand how it works.
This guide covers everything you need to know about object methods and this: how to create methods, how this is resolved in every context, how arrow functions change the rules, and the common mistakes that trip up developers at every level.
Adding Methods to Objects
A method is simply a function that is a property of an object. You can add methods to objects in several ways.
Function as a Property Value
The most explicit way is to assign a function expression to an object property:
let user = {
name: "Alice",
age: 30
};
user.sayHi = function() {
console.log("Hello!");
};
user.sayHi(); // "Hello!"
You can also define the method directly inside the object literal:
let user = {
name: "Alice",
age: 30,
sayHi: function() {
console.log("Hello!");
}
};
user.sayHi(); // "Hello!"
Using a Pre-Declared Function
You can assign an existing function as an object method:
function greet() {
console.log("Hello from a standalone function!");
}
let user = {
name: "Alice"
};
user.sayHi = greet;
user.sayHi(); // "Hello from a standalone function!"
The function greet and the method user.sayHi point to the same function object. The function does not get copied; the property stores a reference to it.
Method Shorthand Syntax
ES6 introduced a shorter syntax for defining methods inside object literals. Instead of writing propertyName: function() { ... }, you can omit the : function part entirely:
let user = {
name: "Alice",
age: 30,
// Method shorthand: preferred in modern JavaScript
sayHi() {
console.log("Hello!");
},
// Equivalent long form
sayBye: function() {
console.log("Goodbye!");
}
};
user.sayHi(); // "Hello!"
user.sayBye(); // "Goodbye!"
Both forms create a method on the object, and for almost all practical purposes they behave identically. The shorthand version is cleaner, more readable, and is the standard convention in modern JavaScript.
Always use the method shorthand syntax when defining methods in object literals. It is more concise, easier to read, and is the universally accepted modern practice.
There is a subtle technical difference: shorthand methods have access to super (used in class inheritance contexts), while the long form does not. This rarely matters in plain objects, but it is another reason to prefer the shorthand.
"this" in Methods: Accessing the Object
Methods often need to access the data stored in the object they belong to. For example, a user.introduce() method needs to know the user's name. The keyword this provides that access.
Inside a method, this refers to the object the method was called on (the object "before the dot").
let user = {
name: "Alice",
age: 30,
introduce() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
}
};
user.introduce();
// "Hi, I'm Alice and I'm 30 years old."
When user.introduce() is called, this inside the method refers to user. So this.name is user.name ("Alice") and this.age is user.age (30).
Why Not Just Use the Variable Name Directly?
You might wonder: why not just write user.name instead of this.name? Technically, you can:
let user = {
name: "Alice",
introduce() {
console.log(`Hi, I'm ${user.name}`); // Works, but fragile
}
};
user.introduce(); // "Hi, I'm Alice"
But this approach is fragile and unreliable:
let user = {
name: "Alice",
introduce() {
console.log(`Hi, I'm ${user.name}`);
}
};
let admin = user;
user = null; // The original variable is gone
admin.introduce(); // TypeError: Cannot read properties of null
The method uses user.name, but user has been set to null. Even though the object still exists (referenced by admin), the method breaks because it hard-coded the variable name user.
Using this avoids this problem entirely:
let user = {
name: "Alice",
introduce() {
console.log(`Hi, I'm ${this.name}`); // Uses this, not the variable name
}
};
let admin = user;
user = null;
admin.introduce(); // "Hi, I'm Alice" (works perfectly)
this always refers to the object the method was called on, regardless of which variable was used to make the call. When we call admin.introduce(), this is admin, which points to the object.
"this" Is Not Bound: Determined at Call Time
This is the most important concept to understand about this in JavaScript. Unlike languages like Java, C#, or Python (where self is fixed), JavaScript's this is not determined when the function is written or defined. It is determined at the moment the function is called, based on how it is called.
The same function can have different values of this depending on the calling context:
let alice = { name: "Alice" };
let bob = { name: "Bob" };
function introduce() {
console.log(`Hi, I'm ${this.name}`);
}
// Assign the SAME function to both objects
alice.greet = introduce;
bob.greet = introduce;
alice.greet(); // "Hi, I'm Alice" (this is alice)
bob.greet(); // "Hi, I'm Bob" (this is bob)
The function introduce is defined once. But when called as alice.greet(), this is alice. When called as bob.greet(), this is bob. The object before the dot becomes this.
The Dot Rule
The simplest way to determine this is the dot rule: in a method call obj.method(), this equals obj. The object immediately before the dot (or bracket notation) is what this refers to.
let calculator = {
value: 0,
add(n) {
this.value += n;
return this; // Return the object for chaining
},
subtract(n) {
this.value -= n;
return this;
},
result() {
console.log(this.value);
}
};
calculator.add(10).add(5).subtract(3).result();
// 12
Each call in the chain returns this (the calculator object), so the next method is also called on calculator.
"this" in Different Contexts
The value of this changes depending on where and how a function is called. Let's examine every context.
Global Context (Outside Any Function)
In the global scope (outside of any function), this refers to the global object:
// In a browser:
console.log(this === window); // true
// In Node.js (CommonJS module):
console.log(this === module.exports); // true (at the top level of a module)
In ES modules, the top-level this is undefined:
// In an ES module (browser or Node.js with type="module"):
console.log(this); // undefined
Regular Function Calls (No Object)
When a regular function is called without an object (not as a method), this depends on whether strict mode is active:
function showThis() {
console.log(this);
}
showThis();
In non-strict mode (sloppy mode): this is the global object (window in browsers, global in Node.js):
// Non-strict mode
function showThis() {
console.log(this === window); // true (in browser)
}
showThis();
In strict mode: this is undefined:
"use strict";
function showThis() {
console.log(this); // undefined
}
showThis();
thisIn strict mode, calling a function without an object context gives this the value undefined instead of the global object. This is a safety feature that prevents accidental modification of global state. Since ES modules and classes are always in strict mode, this is the behavior you will encounter most often in modern code.
Method Calls (Object Before the Dot)
As we have already covered, when a function is called as a method of an object, this is the object before the dot:
let user = {
name: "Alice",
greet() {
console.log(this.name);
}
};
user.greet(); // "Alice" (this is user)
Bracket Notation Works the Same Way
let user = {
name: "Alice",
greet() {
console.log(this.name);
}
};
user["greet"](); // "Alice" (this is still user)
Constructor Calls (with new)
When a function is called with the new keyword, this refers to the newly created object:
function User(name) {
// this = {} (implicitly created new object)
this.name = name;
this.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
// return this (implicit)
}
let alice = new User("Alice");
alice.greet(); // "Hi, I'm Alice"
Event Handlers (DOM)
In a DOM event handler, this refers to the element that the handler is attached to:
const button = document.getElementById("myButton");
button.addEventListener("click", function() {
console.log(this); // The button element
console.log(this.id); // "myButton"
this.style.color = "red"; // Works (this is the button)
});
Explicit Binding: call, apply, bind
You can explicitly set the value of this using call, apply, or bind:
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
let alice = { name: "Alice" };
let bob = { name: "Bob" };
// call: pass arguments individually
greet.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
greet.call(bob, "Hey", "."); // "Hey, I'm Bob."
// apply: pass arguments as an array
greet.apply(alice, ["Hi", "!!"]); // "Hi, I'm Alice!!"
// bind: returns a NEW function with this permanently set
let aliceGreet = greet.bind(alice, "Howdy");
aliceGreet("?"); // "Howdy, I'm Alice?"
// Once bound, this cannot be changed
let rebind = aliceGreet.bind(bob);
rebind("!"); // "Howdy, I'm Alice!" (still alice!)
Summary Table: "this" in Every Context
| Context | this Value |
|---|---|
| Global scope (non-strict) | Global object (window / global) |
| Global scope (strict / ES module) | undefined |
| Regular function call (non-strict) | Global object |
| Regular function call (strict) | undefined |
Method call (obj.method()) | The object before the dot (obj) |
Constructor call (new Func()) | The newly created object |
| Event handler (DOM) | The element the handler is attached to |
call / apply | The first argument passed to them |
bind | The first argument (permanently bound) |
| Arrow function | Inherited from the enclosing lexical scope |
Arrow Functions and "this" (No Own this)
Arrow functions are fundamentally different from regular functions when it comes to this. An arrow function does not have its own this. Instead, it inherits this from the enclosing scope at the time the arrow function is defined (not called).
This is called lexical this binding.
let user = {
name: "Alice",
greetRegular: function() {
console.log(`Regular: ${this.name}`);
},
greetArrow: () => {
console.log(`Arrow: ${this.name}`);
}
};
user.greetRegular(); // "Regular: Alice" (this is user)
user.greetArrow(); // "Arrow: undefined" (this is NOT user!)
Output:
Regular: Alice
Arrow: undefined
Why does the arrow function give undefined? Because arrow functions do not have their own this. They look up to the enclosing lexical scope where they were created. In this case, the arrow function is defined inside an object literal. Object literals do not create a scope. So the enclosing scope is the global scope (or module scope), where this.name is undefined.
Arrow functions as direct methods on object literals will almost certainly give you the wrong this. Always use the regular method shorthand for object methods.
Where Arrow Functions Shine: Preserving this
Arrow functions are incredibly useful when you want to preserve this from an outer function. This is their primary purpose:
let user = {
name: "Alice",
hobbies: ["reading", "coding", "hiking"],
listHobbies() {
// 'this' here is user (regular method call)
this.hobbies.forEach((hobby) => {
// Arrow function inherits 'this' from listHobbies
console.log(`${this.name} likes ${hobby}`);
});
}
};
user.listHobbies();
// "Alice likes reading"
// "Alice likes coding"
// "Alice likes hiking"
The arrow function inside forEach inherits this from the listHobbies method, where this is user. This is the exact scenario arrow functions were designed for.
Compare this with a regular function callback:
let user = {
name: "Alice",
hobbies: ["reading", "coding", "hiking"],
listHobbies() {
this.hobbies.forEach(function(hobby) {
// Regular function: this is NOT user!
console.log(`${this.name} likes ${hobby}`);
});
}
};
user.listHobbies();
// "undefined likes reading"
// "undefined likes coding"
// "undefined likes hiking"
The regular function callback has its own this, which is undefined in strict mode (or the global object in non-strict mode), because it is not being called as a method of any object.
Common Mistakes: Losing "this" in Callbacks and Nested Functions
Understanding the pitfalls around this will save you hours of debugging. Here are the most common mistakes and their solutions.
Mistake 1: Passing a Method as a Callback
When you pass an object method as a callback to another function, you lose the object context. The function is called as a plain function, not as a method.
// ❌ WRONG: Losing this by passing method as callback
let user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
setTimeout(user.greet, 1000);
// After 1 second: "Hello, I'm undefined"
Output:
Hello, I'm undefined
Why? When you write setTimeout(user.greet, 1000), you are extracting the function from the object and passing it as a standalone function. setTimeout calls it later as a regular function, not as user.greet(). So this is no longer user.
Fix 1: Wrap in an arrow function
let user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
// ✅ CORRECT: Arrow function preserves the calling context
setTimeout(() => user.greet(), 1000);
// "Hello, I'm Alice"
The arrow function calls user.greet() as a method call (with the dot), so this is user.
Fix 2: Use bind
let user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
// ✅ CORRECT: bind permanently sets this
setTimeout(user.greet.bind(user), 1000);
// "Hello, I'm Alice"
bind creates a new function where this is permanently set to user.
With the arrow function wrapper () => user.greet(), if user is reassigned before the timeout fires, the new object will be used (because user is read at call time). With bind, the original object is captured at bind time and cannot change. Choose based on your needs.
Mistake 2: Extracting a Method into a Variable
Assigning a method to a variable separates it from its object:
// ❌ WRONG: Method loses context when assigned to variable
let user = {
name: "Alice",
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
let greetFunc = user.greet; // Extracting the method
greetFunc(); // "Hi, I'm undefined" (this is not user!)
Output:
Hi, I'm undefined
Fix:
// ✅ CORRECT: Use bind when extracting methods
let greetFunc = user.greet.bind(user);
greetFunc(); // "Hi, I'm Alice"
Mistake 3: "this" in Nested Regular Functions
A regular function inside a method has its own this, which is not inherited from the outer method:
// ❌ WRONG: Nested function has its own this
let user = {
name: "Alice",
friends: ["Bob", "Charlie"],
showFriends() {
this.friends.forEach(function(friend) {
// this is NOT user here: it's undefined (strict) or window (sloppy)
console.log(`${this.name} knows ${friend}`);
});
}
};
user.showFriends();
// "undefined knows Bob"
// "undefined knows Charlie"
Fix 1: Use an arrow function (best solution)
// ✅ CORRECT: Arrow function inherits this from showFriends
let user = {
name: "Alice",
friends: ["Bob", "Charlie"],
showFriends() {
this.friends.forEach((friend) => {
console.log(`${this.name} knows ${friend}`);
});
}
};
user.showFriends();
// "Alice knows Bob"
// "Alice knows Charlie"
Fix 2: Store this in a variable (pre-ES6 pattern)
// ✅ CORRECT: Classic pattern using a variable
let user = {
name: "Alice",
friends: ["Bob", "Charlie"],
showFriends() {
const self = this; // Capture this in a variable
this.friends.forEach(function(friend) {
console.log(`${self.name} knows ${friend}`);
});
}
};
user.showFriends();
// "Alice knows Bob"
// "Alice knows Charlie"
This self = this (or that = this) pattern was the standard solution before arrow functions existed. You will still see it in older codebases. In modern JavaScript, always prefer arrow functions.
Fix 3: Use the thisArg parameter (when available)
Some array methods like forEach, map, and filter accept an optional second argument that sets this inside the callback:
// ✅ CORRECT: Using forEach's thisArg parameter
let user = {
name: "Alice",
friends: ["Bob", "Charlie"],
showFriends() {
this.friends.forEach(function(friend) {
console.log(`${this.name} knows ${friend}`);
}, this); // <-- thisArg
}
};
user.showFriends();
// "Alice knows Bob"
// "Alice knows Charlie"
Mistake 4: Using Arrow Functions Where You Need Dynamic "this"
Arrow functions always inherit this from their lexical scope. This is a problem when you need dynamic this, such as in event handlers:
// ❌ WRONG: Arrow function in event handler
const button = document.getElementById("myButton");
button.addEventListener("click", () => {
// this is NOT the button (it's the enclosing scope's this)
console.log(this); // window or undefined (depending on context)
this.classList.add("clicked"); // TypeError!
});
Fix:
// ✅ CORRECT: Regular function gets the element as this
button.addEventListener("click", function() {
console.log(this); // The button element
this.classList.add("clicked"); // Works!
});
Or use event.currentTarget instead of relying on this:
// ✅ ALSO CORRECT: Use event parameter instead of this
button.addEventListener("click", (event) => {
console.log(event.currentTarget); // The button element
event.currentTarget.classList.add("clicked");
});
Mistake 5: "this" in a Method Returned from Another Method
// ❌ WRONG: Returning a method loses context
let user = {
name: "Alice",
getGreeter() {
return this.greet; // Returns the function, not the bound method
},
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
let greeter = user.getGreeter();
greeter(); // "Hi, I'm undefined"
Fix:
// ✅ CORRECT: Return a bound function or an arrow function
let user = {
name: "Alice",
getGreeter() {
return () => this.greet(); // Arrow preserves this from getGreeter
},
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
let greeter = user.getGreeter();
greeter(); // "Hi, I'm Alice"
Or using bind:
getGreeter() {
return this.greet.bind(this);
}
Quick Decision Guide: "this" Problems
When you encounter a this issue, ask yourself:
1. Is the function being called with a dot? (obj.method())
Then this is obj. You are fine.
2. Is the function being called without an object? (func())
Then this is undefined (strict) or the global object (sloppy). You need to bind it or wrap it.
3. Is the function an arrow function?
Then this comes from the enclosing scope. Make sure that scope has the this you want.
4. Was call, apply, or bind used?
Then this is whatever was explicitly passed.
5. Was new used?
Then this is the newly created object.
Summary
- Object methods are functions stored as properties of objects. Use the shorthand syntax (
method() { }) in modern JavaScript. thisinside a method refers to the object the method was called on (the object "before the dot").thisis determined at call time, not at definition time. The same function can have differentthisvalues depending on how it is called.- In regular function calls (without an object),
thisisundefinedin strict mode and the global object in non-strict mode. - Arrow functions do not have their own
this. They inherit it from the enclosing lexical scope. This makes them ideal for callbacks inside methods, but unsuitable as object methods themselves. - The most common mistake is losing
thiswhen passing a method as a callback (tosetTimeout,addEventListener,forEach, etc.). Fix it with an arrow function wrapper orbind. - Never use arrow functions as direct object methods or when you need dynamic
this(like DOM event handlers that need to reference the element). - Use
call,apply, orbindwhen you need to explicitly control whatthisrefers to.
Mastering this is a milestone in your JavaScript journey. Once you internalize the call-time determination rule and the arrow function exception, bugs related to this will become easy to diagnose and fix.