JavaScript Object Methods

An object method is simply a function that lives inside an object as one of its properties. Instead of writing standalone functions that take an object as an argument, you attach behavior directly to the data it operates on, so car.describe() reads more naturally than describe(car). Methods are the foundation of object-oriented patterns in JavaScript, and understanding exactly how they work — especially how the this keyword is bound — will save you from some of the most common bugs in the language.

Overview: What Is an Object Method?

In JavaScript, functions are values. Because a function is just a value, you can store one as the value of an object property, exactly like you would store a string or a number. When a property’s value happens to be a function, and you access it through the object (for example obj.doSomething()), we call that property a method rather than just a property.

The key idea that separates a method from an ordinary function is how it is called, not how it is defined. JavaScript does not have a special “method” data type — under the hood a method is a normal function reference sitting in a property slot. What makes it behave like a method is the call site: when you write obj.doSomething(), JavaScript evaluates obj.doSomething to get the function, then calls it with obj automatically passed in as the special this value for that call. This is why the same function can behave differently depending on how it is invoked — this is not fixed at definition time, it is determined at call time by the object to the left of the dot (or bracket).

This matters because it means a method can read and modify the object it belongs to using this, without needing to know the object’s name in advance. That is what lets you copy an object, pass it around, or create many similar objects, and have each one’s methods automatically operate on the correct instance.

Syntax

There are several ways to attach a function to an object as a method. All of them end up storing a function as a property value, but the syntax and a few subtle behaviors differ.

Style Example Notes
Function expression greet: function () { ... } Classic pre-ES6 syntax. Still valid, more verbose.
Method shorthand (ES6) greet() { ... } Preferred modern syntax. Behaves like a function expression, but slightly leaner and marked internally as a method.
Computed method name [dynamicKey]() { ... } The property name is computed from an expression at object-creation time.
Async method async load() { ... } Method that returns a Promise; use await inside it.
Generator method *iterate() { ... } Method that returns an iterator via yield.
Arrow function property greet: () => { ... } Technically stores a function, but it does not get its own this — avoid for methods that need this.

In practice, the ES6 method shorthand is what you should reach for by default: propertyName(parameters) { statements }. It omits the function keyword and the colon entirely.

Examples

Example 1: A basic method using this

const car = {
  brand: "Toyota",
  model: "Corolla",
  year: 2022,
  describe: function () {
    return `${this.year} ${this.brand} ${this.model}`;
  }
};

console.log(car.describe());
Output:
2022 Toyota Corolla

The describe property holds a function. Because we call it as car.describe(), JavaScript binds this to car for the duration of that call, so this.year, this.brand, and this.model resolve to the matching properties on car.

Example 2: Method shorthand and mutating state

const counter = {
  count: 0,
  increment() {
    this.count += 1;
    return this.count;
  },
  reset() {
    this.count = 0;
  }
};

console.log(counter.increment());
console.log(counter.increment());
counter.reset();
console.log(counter.count);
Output:
1
2
0

This uses the ES6 shorthand: increment() is equivalent to increment: function () { ... }. Each call to counter.increment() mutates counter.count through this, and the change is visible afterward because this refers to the actual counter object, not a copy of it.

Example 3: A computed method name calling another method

const key = "getFullName";

const user = {
  firstName: "Ada",
  lastName: "Lovelace",
  [key]() {
    return `${this.firstName} ${this.lastName}`;
  },
  greet() {
    return `Hello, ${this.getFullName()}!`;
  }
};

console.log(user.getFullName());
console.log(user.greet());
Output:
Ada Lovelace
Hello, Ada Lovelace!

The square brackets [key]() tell JavaScript to use the value of the key variable (the string "getFullName") as the property name, so this defines a method literally named getFullName. Notice that greet calls this.getFullName() — one method calling a sibling method through this is an extremely common pattern for composing behavior inside an object.

Under the Hood: How this Gets Bound

It is worth being precise about what actually happens when JavaScript executes obj.method():

  1. The engine evaluates the expression before the parentheses, obj.method, which looks up the method property and produces a reference to the function, remembering that it was fetched from obj.
  2. Because the call uses that member-access reference, JavaScript performs what the specification calls a “method call”: it invokes the function and sets this inside the function body to obj for that single invocation.
  3. Once the function starts running, this behaves like any other local binding for the lifetime of that call — it is not re-evaluated line by line, it was fixed the moment the call happened.

Crucially, this binding is a property of how the function is called, not where it was defined or which object it “belongs” to conceptually. If you detach a method from its object and call it without a receiver, it loses that binding entirely:

"use strict";

const wallet = {
  balance: 100,
  getBalance() {
    return this.balance;
  }
};

console.log(wallet.getBalance());

const getBalance = wallet.getBalance;
try {
  console.log(getBalance());
} catch (error) {
  console.log(error.message);
}
Output:
100
Cannot read properties of undefined (reading 'balance')

The first call, wallet.getBalance(), is a proper method call, so this is wallet and it returns 100. The second call takes the same function but invokes it with no object in front of the dot, so in strict mode this is undefined, and reading .balance off undefined throws a TypeError. In non-strict (sloppy) code the same call would silently set this to the global object instead of throwing, which is arguably worse because it fails quietly.

Common Mistakes

Mistake 1: Using an arrow function as a method

Arrow functions do not have their own this. They capture this lexically from the surrounding scope where they were defined, not from how they are called. That makes them a poor fit for object methods that need to read the object’s own properties.

const timer = {
  seconds: 0,
  start: () => {
    console.log(this.seconds);
  }
};

timer.start();
Output:
undefined

Because start is an arrow function, its this is whatever this was in the scope surrounding the object literal (at the top of a Node module, that is module.exports, an object with no seconds property) — it is never rebound to timer, no matter how you call timer.start(). The fix is to use a regular method (shorthand or function expression) so this is bound by the call site instead:

const timer2 = {
  seconds: 0,
  start() {
    console.log(this.seconds);
  }
};

timer2.start();
Output:
0

Mistake 2: Passing a method reference around and losing its receiver

Whenever you extract a method off an object — assign it to a variable, pass it as a callback to setTimeout, an event listener, or .map() — you strip away the object that used to sit before the dot. The function itself remembers nothing about where it came from, so this will not automatically be the original object anymore (see the wallet.getBalance example above). The standard fix is Function.prototype.bind(), which permanently locks this to a chosen object:

const wallet3 = {
  balance: 500,
  getBalance() {
    return this.balance;
  }
};

const bound = wallet3.getBalance.bind(wallet3);
console.log(bound());
Output:
500

bind() returns a new function that always calls the original with this set to wallet3, no matter how the new function is later invoked — so it is safe to hand off to a callback or store separately.

Best Practices

  • Prefer ES6 method shorthand (name() { ... }) over name: function () { ... } — it is shorter and clearly signals “this is a method”.
  • Never use an arrow function for a method body that needs to read or mutate the object’s own properties via this.
  • When passing a method as a callback (event handlers, setTimeout, array callbacks), either wrap it in an arrow function at the call site (() => obj.method()) or pre-bind it with .bind(obj).
  • Keep methods focused on one responsibility; have methods call sibling methods through this instead of duplicating logic.
  • Use computed method names ([expr]() {}) sparingly, only when the method name genuinely needs to be dynamic — a hard-coded name is easier to search for and read.
  • Reach for class syntax once an object has many methods and you need multiple similar instances; classes are effectively method-storage on a shared prototype, so understanding plain-object methods first makes classes much easier to learn.
  • Add "use strict"; (or use ES modules, which are strict by default) so that calling a method without a receiver fails loudly with a TypeError instead of silently touching the global object.

Practice Exercises

  1. Create an object literal called rectangle with width and height properties, plus a method area() that returns width * height using this. Log the result of calling it.
  2. Take the counter object from Example 2 and add a new method decrement() that lowers count by 1 and returns the new value. Then call increment() twice and decrement() once, and predict the final value of counter.count before checking it.
  3. Given const person = { name: "Sam", sayHi() { console.log(this.name); } };, extract sayHi into a standalone variable and call it directly. Explain in your own words why the output differs from calling person.sayHi(), then fix it using bind() so the standalone call also logs "Sam".

Summary

  • An object method is a function stored as a property value; what makes it act like a method is being called through the object (obj.method()), which binds this to that object for the call.
  • ES6 method shorthand (name() { ... }) is the modern, preferred way to define methods on object literals.
  • this is determined at call time by the call site, not at function-definition time — extracting a method and calling it without a receiver loses the intended this.
  • Arrow functions capture this lexically from their enclosing scope and should not be used as object methods that rely on this.
  • .bind(obj) creates a new function permanently locked to a given this, which is the standard fix when passing methods as callbacks.
  • Methods can call other methods on the same object through this, letting you compose behavior cleanly inside a single object.