Understanding JavaScript's `this` Keyword in Depth

JavaScript's this keyword is a comon source of confusion. Unlike many other languages, the value of this inside a function is not determined by where the function is defined, but by how it is called. In other words, the invocation context decides what this points to.

In JavaScript, a function can be called in several ways: direct call, method call, and constructor call (with new). Additionally, there are special calls using bind(), call(), and apply(). ES6 introduced arrow functions, which have a different behavior regarding this. This article will explore these scenarios.

1. Direct Call

A direct call is simply invoking a function as functionName(...). In this case, this inside the function refers to the global object. In a browser, the global object is window; in Node.js, it is global.

Example:

// Simple compatibility for browser and Node.js global object
const _global = typeof window === "undefined" ? global : window;

function test() {
    console.log(this === _global); // true
}

test(); // Direct call

Note that direct call is not limited to the global scope. Calling a function as functionName(...) from any scope is considered a direct call, and this still refers to the global object.

(function(_global) {
    // IIFE to limit scope
    function test() {
        console.log(this === _global); // true
    }

    test(); // Direct call within a non-global scope
})(typeof window === "undefined" ? global : window);

Impact of bind() on Direct Calls

The Function.prototype.bind() method creates a new function with a fixed this value. Regardless of how the bound function is called, this will always point to the bound object.

const obj = {};

function test() {
    console.log(this === obj);
}

const testObj = test.bind(obj);
test(); // false
testObj(); // true

To illustrate how bind() works, consider this simple simulation:

function myBind(func, target) {
    return function() {
        return func.apply(target, arguments);
    };
}

const testObj = myBind(test, obj);
test(); // false
testObj(); // true

The simulation uses a closure to retain target and then applies func using apply(). While the native bind() implementation is more efficient, this example shows its core logic.

Impact of call() and apply()

Function.prototype.call() and Function.prototype.apply() also allow you to set the this value for a function call. Their first argument specifies the this context.

However, if the function is already bound using bind(), call() and apply() will not override the bound this.

const obj = {};

function test() {
    console.log(this === obj);
}

const testObj = test.bind({});
test.apply(obj); // true

testObj.apply(obj); // false

2. Method Call

A method call occurs when a function is called as a property of an object, using the syntax object.method(...). In this case, this refers to the object that owns the method. However, bind() can also affect this behavior.

const obj = {
    test() {
        console.log(this === obj);
    }
};

obj.test2 = function() {
    console.log(this === obj);
};

function t() {
    console.log(this === obj);
}
obj.test3 = t;

obj.test4 = (function() {
    console.log(this === obj);
}).bind({});

obj.test();  // true
obj.test2(); // true
obj.test3(); // true
obj.test4(); // false

Even if a function is defined separately and then assigned to an object, this is determined by the call pattern, not the definition.

When this Points to the Global Object in a Method

This happens when a method is called directly instead of as a method. For example:

const obj = {
    test() {
        console.log(this === obj);
    }
};

const t = obj.test;
t(); // false

This is a common pitfall when passing object methods as callbacks. Consider this example with jQuery:

class Handlers {
    constructor(data, $button) {
        this.data = data;
        $button.on("click", this.onButtonClick);
    }

    onButtonClick(e) {
        console.log(this.data);
    }
}

const handlers = new Handlers("string data", $("#someButton"));
// Clicking the button outputs undefined, not "string data"

The issue is that this.onButtonClick is passed as a callback and later invoked as a direct call, losing its original context. To fix this, you can:

// Solution 1: Store a reference to this
var _this = this;
$button.on("click", function() {
    _this.onButtonClick();
});

// Solution 2: Use bind()
$button.on("click", this.onButtonClick.bind(this));

// Solution 3: Use an arrow function (caution with jQuery)
$button.on("click", e => this.onButtonClick(e));

Be careful when using arrow functions with jQuery, as this inside the callback will not be the target element.

3. Constructor Call with new

Before ES6, any function could be used as a constructor with the new keyword. When a function is called with new, a new object is created, and this points to that new object.

var data = "Hi"; // Global variable

function AClass(data) {
    this.data = data;
}

var a = new AClass("Hello World");
console.log(a.data); // Hello World
console.log(data);   // Hi

var b = new AClass("Hello World");
console.log(a === b); // false

ES6 introduced classes, but they still behave similarly. However, methods defined inside a class cannot be called with new.

4. Arrow Functions and this

Arrow functions do not have their own this binding. The value of this inside an arrow function is inherited from the enclosing non-arrow function or context.

const obj = {
    test() {
        const arrow = () => {
            // `this` is taken from test()'s context
            console.log(this === obj);
        };
        arrow();
    },

    getArrow() {
        return () => {
            // `this` is taken from getArrow()'s context
            console.log(this === obj);
        };
    }
};

obj.test(); // true

const arrow = obj.getArrow();
arrow(); // true

Arrow functions eliminate the need for var _this = this closures. Babel's transpilation of the above example illustrates this:

// ES6
const obj = {
    getArrow() {
        return () => {
            console.log(this === obj);
        };
    }
};

// Transpiled to ES5
var obj = {
    getArrow: function getArrow() {
        var _this = this;
        return function () {
            console.log(_this === obj);
        };
    }
};

Arrow functions cannot be used as constructors and are not affected by bind(), call(), or apply() to change their this.


Original article by 边城 on SegmentFault

Tags: javascript this scope Binding arrow functions

Posted on Fri, 31 Jul 2026 16:18:10 +0000 by leon_zilber