Skip to content
On this page

Method

Binding a function within an object is called a method of that object.

In JavaScript, an object is defined like this:

javascript
let xiaoming = {
    name: 'Ming',
    birth: 1990
};

However, if we bind a function to xiaoming, we can do more. For example, writing an age() method that returns xiaoming's age:

javascript
let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: function () {
        let y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // This year it's 25; next year it will be 26.

A function bound to an object is called a method. It’s similar to a regular function, but it uses a special variable called this internally. What is this?

Inside a method, this is a special variable that always points to the current object, which in this case is the variable xiaoming. Thus, this.birth accesses xiaoming's birth property.

Let’s break it down:

javascript
function getAge() {
    let y = new Date().getFullYear();
    return y - this.birth;
}

let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25, normal result
getAge(); // NaN

Why does calling the function getAge() return NaN? Notice we’ve entered a significant pitfall in JavaScript.

When a function calls this, to whom does it refer?

The answer is: it depends on the context!

If called as a method of an object, like xiaoming.age(), this points to the calling object, xiaoming, which aligns with our expectations.

If called independently, like getAge(), then this points to the global object, which is window.

What a mess!

Even worse is when you do this:

javascript
let fn = xiaoming.age; // Getting xiaoming's age function
fn(); // NaN

This also fails! To ensure this points correctly, you must use the obj.xxx() format!

Due to this significant design flaw, fixing it isn’t straightforward. ECMA decided that in strict mode, a function's this should point to undefined. Therefore, in strict mode, you will encounter an error:

javascript
'use strict';

let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: function () {
        let y = new Date().getFullYear();
        return y - this.birth;
    }
};

let fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined

This decision only exposes the error but doesn’t solve the issue of where this should point.

Sometimes, if you, as a refactoring enthusiast, restructure the method:

javascript
'use strict';

let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            let y = new Date().getFullYear();
            return y - this.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

The result again throws an error! The reason is that the this pointer only points to xiaoming within the age method. The function defined inside has its this pointing to undefined (or the global object in non-strict mode).

There is a solution: capture this with a that variable:

javascript
'use strict';

let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: function () {
        let that = this; // Capture this at the beginning of the method
        function getAgeFromBirth() {
            let y = new Date().getFullYear();
            return y - that.birth; // Use that instead of this
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // 25

Using let that = this; allows you to safely define other functions within the method without cluttering everything into one method.

Apply

Although in an independent function call, this points to undefined or window depending on strict mode, we can still control what this refers to!

To specify which object a function's this points to, you can use the function's apply method. It takes two parameters: the first is the this value you want to bind, and the second is an array representing the function's parameters.

Use apply to fix the getAge() call:

javascript
function getAge() {
    let y = new Date().getFullYear();
    return y - this.birth;
}

let xiaoming = {
    name: 'Ming',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this points to xiaoming, parameters empty

Another method similar to apply() is call(), with the only difference being:

  • apply() packs parameters into an array before passing them;
  • call() passes parameters individually.

For example, calling Math.max(3, 5, 4) can be achieved with apply() and call() as follows:

javascript
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

For ordinary function calls, we typically bind this to null.

Decorators

By utilizing apply(), we can also dynamically change a function's behavior.

All JavaScript objects are dynamic, meaning we can even reassign built-in functions.

Now assume we want to track how many times parseInt() has been called. We could manually count each call, but that’s inefficient. The best approach is to replace the default parseInt() with our own function:

javascript
'use strict';

let count = 0;
let oldParseInt = parseInt; // Save the original function

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // Call the original function
};

// Test:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
Method has loaded