How the `this` Keyword Works in JavaScript: Regular Functions, Arrow Functions, and Methods
In JavaScript, the value of this is determined by how a function is invoked rather than where it is defined, following five binding rules that prioritize new binding, explicit binding, implicit binding, and default binding, while arrow functions capture this lexically from their surrounding scope.
Understanding how the this keyword works in JavaScript is essential for mastering object-oriented patterns and avoiding common context-related bugs. The leonardomso/33-js-concepts repository provides an exhaustive test suite in tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js that demonstrates these behaviors through executable specifications. This guide explains the five binding rules that govern this across different invocation contexts based on the actual source code implementation.
The Five Binding Rules for this
JavaScript determines the value of this through five distinct binding rules, each triggered by a specific invocation pattern. The test suite in this-call-apply-bind.test.js validates these rules with test cases such as "should have different this values depending on how function is called" and "Rule 5: Arrow Functions (Lexical this)".
Default Binding in Standalone Functions
When a regular function is invoked without any context—simply as a standalone function—default binding applies. In strict mode, this is set to undefined; in non-strict mode, it defaults to the global object.
function showThis() {
return this;
}
// In strict mode
expect(showThis()).toBeUndefined();
Implicit Binding in Method Calls
When a function is called as a property of an object using dot notation (obj.method()), implicit binding sets this to the object that appears immediately before the dot. This rule applies to regular functions assigned as object properties.
const obj = {
name: 'Object',
showThis: function() { return this; }
};
expect(obj.showThis()).toBe(obj);
Explicit Binding with call, apply, and bind
JavaScript provides three methods for manually specifying the this context: explicit binding. The call() method invokes the function immediately with a given this value and arguments passed individually, while apply() accepts arguments as an array. The bind() method returns a new function with this permanently fixed, ignoring subsequent attempts to rebind.
function greet() {
return `Hello, ${this.name}!`;
}
const alice = { name: 'Alice' };
expect(greet.call(alice)).toBe('Hello, Alice!');
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const bob = { name: 'Bob' };
expect(introduce.apply(bob, ['Hi', '!'])).toBe('Hi, I\'m Bob!');
function getName() {
return this.name;
}
const boundGetName = getName.bind(alice);
expect(boundGetName()).toBe('Alice');
expect(boundGetName.call({ name: 'Bob' })).toBe('Alice'); // call ignored
new Binding in Constructors
When a function is invoked with the new keyword, JavaScript creates a fresh object and binds this to that new instance. This new binding takes precedence over all other binding types and allows constructors to initialize instance properties.
class Rectangle {
constructor(w, h) {
this.width = w;
this.height = h;
}
getArea() {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 5);
expect(rect.getArea()).toBe(50);
Lexical Binding in Arrow Functions
Arrow functions do not have their own this binding. Instead, they capture the this value from their enclosing lexical scope at definition time. This makes arrow functions immune to rebinding via call, apply, or bind, and ideal for preserving context in callbacks.
const obj = {
name: 'Object',
method() {
const arrow = () => this.name;
return arrow();
}
};
expect(obj.method()).toBe('Object');
Arrow functions ignore explicit binding attempts:
const obj = {
name: 'Original',
getArrow() {
return () => this.name;
}
};
const arrow = obj.getArrow();
expect(arrow.call({ name: 'Other' })).toBe('Original');
Binding Priority and Rule Precedence
The five binding rules follow a strict hierarchy of precedence: new binding > explicit binding > implicit binding > default binding. Arrow functions operate outside this hierarchy entirely, as they close over the lexical this from their creation context rather than participating in runtime binding determination.
How this Works in Different Function Types
Regular Functions and Dynamic Context
Regular functions utilize dynamic binding, meaning the value of this depends entirely on the invocation pattern at runtime. The same function can reference different objects as this when called as a method versus a standalone function, as demonstrated in the test "should have different this values depending on how function is called".
Arrow Functions and Lexical Scope
Arrow functions inherit this from their enclosing scope, making them predictably bound to their creation context. This behavior is particularly valuable for callback functions where preserving the outer method's context is necessary.
class Counter {
constructor() {
this.count = 0;
}
incrementWithArrow() {
[1, 2, 3].forEach(() => {
this.count++;
});
}
}
const c = new Counter();
c.incrementWithArrow();
expect(c.count).toBe(3);
Source Code Reference
The implementation details and test cases for these binding rules are located in tests/object-oriented/this-call-apply-bind/this-call-apply-bind.test.js within the leonardomso/33-js-concepts repository. This file contains exhaustive unit tests covering edge cases for all five binding rules, arrow function behavior, and interaction between different invocation patterns. The index.js entry point exposes these modules as part of the package build, ensuring the examples are validated against the library's public API.
Summary
- Invocation determines binding: The value of
thisdepends on how a function is called, not where it is defined. - Five binding rules govern
this: Default, implicit, explicit,new, and lexical binding each apply to specific invocation patterns. - Arrow functions capture lexical
this: They inheritthisfrom their surrounding scope and ignore attempts to rebind withcall,apply, orbind. - Binding precedence matters:
newbinding overrides explicit binding, which overrides implicit binding, which overrides default binding. - Test suite validation: The
33-js-conceptsrepository provides executable proof of these behaviors inthis-call-apply-bind.test.js.
Frequently Asked Questions
What is the difference between this in arrow functions and regular functions?
Regular functions use dynamic binding where this is determined by how the function is invoked at runtime, while arrow functions use lexical binding where this is captured from the surrounding scope at definition time. Arrow functions cannot be rebound using call, apply, or bind, making them ideal for callbacks where the outer context must be preserved.
How does the new keyword affect this binding?
When a function is invoked with new, JavaScript creates a new empty object, sets this to reference that object within the constructor, and returns the object implicitly unless the constructor returns a different object explicitly. This new binding has the highest priority and overrides both explicit and implicit binding rules.
Can you change this inside an arrow function using call or bind?
No, arrow functions do not have their own this binding and therefore ignore attempts to set this via call, apply, or bind. According to the test cases in this-call-apply-bind.test.js, calling an arrow function with call({ name: 'Other' }) will still return the lexical this value captured at creation time, not the object passed to call.
Why is this undefined in my JavaScript function?
If this is undefined in a standalone function call, your code is likely running in strict mode where default binding sets this to undefined rather than the global object. This is the expected behavior for default binding in modern JavaScript and helps prevent accidental global variable pollution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →