In this short tutorial we are going to learn how to use "this" in Javascript with 7 different examples …
If the function is defined as an arrow function: {: #arrow-functions }
const arrowFunction = () => {
console.log(this);
};
In this case, the value of this
is always the same as this
in the parent scope:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
Arrow functions are great because the inner value of this
can't be changed, it's always the same
as the outer this
.
Other examples
With arrow functions, the value of this
can't be changed with bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
With arrow functions, the value of this
can't be changed with call
or apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
With arrow functions, the value of this
can't be changed by calling the function as a member of
another object:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
With arrow functions, the value of this
can't be changed by calling the function as a
constructor:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
'Bound' instance methods
With instance methods, if you want to ensure this
always refers to the class instance, the best
way is to use arrow functions and class
fields:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
This pattern is really useful when using instance methods as event listeners in components (such as
React components, or web components).
The above might feel like it's breaking the "this
will be the same as this
in the parent scope"
rule, but it starts to make sense if you think of class fields as syntactic sugar for setting things
in the constructor:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
Alternative pattens involve binding an existing function in the constructor, or assigning the
function in the constructor. If you can't use class fields for some reason, assigning functions in
the constructor is a reasonable alternative:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
Otherwise, if the function/class is called with new
: {: #new }
new Whatever();
The above will call Whatever
(or its constructor function if it's a class) with this
set to the
result of Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
The same is true for older-style constructors:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
Other examples
When called with new
, the value of this
can't be changed with bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
When called with new
, the value of this
can't be changed by calling the function as a member
of another object:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Otherwise, if the function has a 'bound' this
value: {: #bound }
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Whenever boundFunction
is called, its this
value will be the object passed to bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Avoid using bind
to bind a function to its outer this
. Instead, use arrow functions, as they make this
clear from the function declaration, rather than
something that happens later in the code.
Don't use bind
to set this
to some value unrelated to the parent object; it's usually unexpected and it's why this
gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.
Other examples
When calling a bound function, the value of this
can't be changed with call
or
apply
:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
When calling a bound function, the value of this
can't be changed by calling the function as a
member of another object:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Otherwise, if this
is set at call-time: {: #call-apply }
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
The value of this
is the object passed to call
/apply
.
Don't use call
/apply
to set this
to some value unrelated to the parent object; it's usually unexpected and it's why this
gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.
Unfortunately this
is set to some other value by things like DOM event listeners, and using it can
result in difficult-to-understand code:
element.addEventListener('click', function (event) {
// Logs `element`, since the DOM spec sets `this` to
// the element the handler is attached to.
console.log(this);
});
I avoid using this
in cases like above, and instead:
element.addEventListener('click', (event) => {
// Ideally, grab it from a parent scope:
console.log(element);
// But if you can't do that, get it from the event object:
console.log(event.currentTarget);
});
Otherwise, if the function is called via a parent object (parent.func()
): {: #object-member }
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
In this case the function is called as a member of obj
, so this
will be obj
. This happens at
call-time, so the link is broken if the function is called without its parent object, or with a
different parent object:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
is false because someMethod
isn't called as a member of obj
. You might
have encountered this gotcha when trying something like this:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
This breaks because the implementation of querySelector
looks at its own this
value and expects
it to be a DOM node of sorts, and the above breaks that connection. To achieve the above correctly:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Fun fact: Not all APIs use this
internally. Console methods like console.log
were changed to
avoid this
references, so log
doesn't need to be bound to console
.
Don't transplant a function onto an object just to set this
to some value unrelated to the parent object; it's usually unexpected and it's why this
gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.
Otherwise, if the function or parent scope is in strict mode: {: #strict }
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
In this case, the value of this
is undefined. 'use strict'
isn't needed in the function if the parent scope is in strict
mode (and all modules are in strict mode).
Don't rely on this. I mean, there are easier ways to get an undefined
value 😀.
Otherwise: {: #otherwise }
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
In this case, the value of this
is the same as globalThis
.
Most folks (including me) call globalThis
the global object, but this isn't 100% technically correct. Here's [Mathias Bynens with the details (https://mathiasbynens.be/notes/globalthis#terminology), including why it's called globalThis
rather than simply global
.
Avoid using this
to reference the global object (yes, I'm still calling it that). Instead, use globalThis
,
which is much more explicit.
Additional resources to learn Javascript:
Get the book: Javascript Challenges
JavaScript: Understanding the Weird Parts
Monster JavaScript Course - 50+ projects and applications
reference sites: https://web.dev/javascript-this/
Top comments (10)
You made a mistake in the constructor:
There are no classes in JavaScript - just objects.
MDN: Classes
i.e. classes are just a convenience for creating objects in JavaScript - nothing more - objects are not tied to some kind of static class membership.
Your second example isn't surprising:
w1
andw2
are separate objects with different references. So your original comparison will fail just asw1 === w2
will fail.Meanwhile the reference returned by
someMethod
is identical to the reference of the object itself.Well I was wrong, I learned that
this
is even available as arrow function that is created inside the class.To understand
this
in arrow functions you need to understand closures/scope.Because
this
inside an arrow function refers to whateverthis
was in the closure at the point in time the arrow function was created.Dissecting ...
this
refers to the object under construction as it is typical for constructor functions.someMethod
property of the object under construction.this
is copied toouterThis
and the IIFE is the closure that creates the function that is assigned tosomeMethod
. But given that all of this is still happening inside the constructor functionthis
is still simply the object under construction.This may make it more clear what is actually happening:
Similarly with Function.prototype.call():
This is why inside functions
this
is called the function context. It's the context that is passed to the function so it can do its job. If the function operates as a method the function context refers to the object to which it was invoked on. But in a constructor function the function context refers to the object under construction. So whatthis
is depends entirely on how a function is invoked. Arrow function expressions change the rules -this
refers to whateverthis
was in the creating closure when the arrow function was created. This means that arrow functions on super-types won't work for sub-types because sub-types are unable to pass in their ownthis
into the arrow function.For an interesting application of closures see OOP with Functions in JavaScript.
Thanks for clarification, I had assumed that IIFE captured
this
asclass
is created in scope ofglobalThis
, but now I understand that fields of class are invoked inside constructor.thanks for the insights and corrections
thanks for the insights and corrections
All you are demonstrating here is that the left hand side object has the same
constructor
as the right hand side object - not:Given that
Object.create
will create an entirely new object (i.e. different from the one being prepared by the constructor function) that statement is incorrect.bind
only works when the function is called as a function -new
uses the function as a constructor function - i.e.this
is the "construction context" (the new object being constructed) - not the "calling context" (an object providing context for a regular function call).The reputation is based on developers educated in class-oriented object orientation being surprised by
this
not relating to a class instance. They shouldn't be surprised asthis
is called the function context in JavaScript.The function context is just often used to emulate method calls (but not always).
EventTarget.addEventListener() actually supports objects directly (since IE 9 (i.e. 2011)).
EventListener.handleEvent()
At times it's useful to "borrow" a method.
Within the browser
self
can by useful (instead onWindow.window
) (Window.self and WorkerGlobalScope.self).Good attempt to summarize, with a few issues here and there.
Also take a look at "this", if it helps you or anyone further.
dev.to/anmshpndy/how-well-do-you-k...
I finally understand this!