Skip to main content

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.

tip

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();
Strict Mode Changes this

In 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

Contextthis 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 / applyThe first argument passed to them
bindThe first argument (permanently bound)
Arrow functionInherited 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.

Never Use Arrow Functions as Object Methods

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.

The Arrow Function Fix Has a Subtle Difference

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.
  • this inside a method refers to the object the method was called on (the object "before the dot").
  • this is determined at call time, not at definition time. The same function can have different this values depending on how it is called.
  • In regular function calls (without an object), this is undefined in 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 this when passing a method as a callback (to setTimeout, addEventListener, forEach, etc.). Fix it with an arrow function wrapper or bind.
  • 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, or bind when you need to explicitly control what this refers 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.