this
is one of the fundamental concepts in JavaScript but it's also one of the most confusing concepts to wrap your head around as well. In this blog, I want to share with you the ways that I use to determine what this
is.
Before going to deep-dive into all the specific rules that can be applied to determine this
, you can remember an easy rule that can be true in most (not all the time) cases. This is how I remember it:
-
this
bound to object when the function is a method of an object. -
this
bound to global object or undefined when the function is not a method.
You can try to think about these 2 rules when you are going through all the examples.
Rules for binding this
:
Default binding
In this rule, we will consider the most common case when calling a function: standalone function invocation.
Consider this code:
function foo() {
console.log(this.a)
}
var a = '2' // If we declare var in global scope => there will be a property with the same name in the global object.
foo() // 2 => Foo is called within the global scope
In this example foo
is called within the global scope so this
will be binded to the global object.
Note: this rule does not apply in 'use strict'
.
Implicit binding
Another rule is: does the call-site have a context object.
Consider:
function foo() {
console.log(this.a)
}
const object = {
a: 42,
foo: foo
}
object.foo() // 42
So foo
is a method of object
then the implicit binding rule says that this
should be binded to the object
.
Only the top/last level object matters to the call-site (where the function is called):
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo // for stays in obj2 => obj2 will be the call-site for foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
Implicit lost
Whenever we pass our function as a callback function, we will lose the binding of this
, which usually means it fallbacks to the default binding (global object or undefined).
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"
In this example, foo is passed as a callback so this
will bound to the call-site where setTimeout
is called.
Or:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"
In this example, bar
is pointing to the foo
function, so when we call bar()
the call-site will depend on where bar
is called, which is the global object in this example.
Explicit binding
Use call
and apply
Consider:
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
The differences between these two are **"C for comma, A for array", which means that you can do:
foo.call(obj, arg1, arg2, arg3)
foo.apply(obj, [arg1, arg2, arg3])
Hard Binding
The implicit lost problem can be solved by doing this, called hard binding
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2
This is such a common pattern, it's provided with built-in util in ES5: Function.prototype.bind
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = foo.bind(obj)
bar() // 2
In ES6, functions provide an optional parameter called "context" which is a work-around for people not to use bind()
:
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
new
binding
Consider:
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
By calling foo(..)
with new
in front of it, we've constructed a new object and set that new object as the this
for the call of foo(..).
Determining this
Is the function called with
new
(new binding)? If so,this
is the newly constructed object.
var bar = new foo()
Is the function called with
call
orapply
(explicit binding), even hidden inside abind
hard binding? If so,this
is the explicitly specified object.
var bar = foo.call( obj2 )
Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so,
this
is that context object.
var bar = obj1.foo()
Otherwise, default the
this
(default binding). If instrict mode
, pickundefined
, otherwise pick theglobal
object.
var bar = foo()
Exceptions
Ignore this
If we pass null
or undefined
to call
, apply
or bind
, those values are effectively ignored, and the default binding rule will be applied here.
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
Note: to be safe in case you want to bind this against the function call which comes from a library or a framework, and that function does make a this
reference. You can accidentally point this
to the global object.
Safer this
Instead of passing in a null
we can pass in an empty object by doing Object.create(null)
You may wonder what the differences are between {}
and Object.create(null)
?
{}
: has the Object.prototype
.
Object.create(null)
is really an empty object, it has nothing so it's considered to be cleaner.
Softening binding
So if you remember hard binding, it's not really flexible as it only points to the specified obj
const foo = bar.bind(obj) // this always bounds to obj
We can construct an alternative utility that works similarly to bind()
called softBind()
.
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call( arguments, 1 ),
bound = function bound() {
return fn.apply(
(!this ||
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
The way I remember it is softBind(obj)
only fallbacks to the obj
if the default this
is global object.
Let's see the usage of softBind()
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- look!!!
fooOBJ.call( obj3 ); // name: obj3 <---- look!
setTimeout( obj2.foo, 10 ); // name: obj <---- falls back to soft-binding
Lexical this
Consider:
function foo() {
setTimeout(() => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
},100);
}
var obj = {
a: 2
};
foo.call( obj ); // 2
When you are using the arrow function, that function will bound to whatever foo
's this
is at its call-time.
Summary
There are 4 rules to determine this:
- Using
new
? Use the newly constructed object - Using
call
,apply
,bind
? Use the specified object - Method of an object? Use that object
- Default: global object and undefined in strict mode.
In most cases, you can just remember:
-
this
bound to object when the function is a method -
this
bound to global object or undefined when the function is not a method.
P/s: If you want to read more post about JavaScript or React, please visit my website: https://kelvinnguyen97.com/blog
Top comments (8)
Awesome article. Only critique I'd make is that you forgot about the industry's best practice for determining
this
: Puttingconsole.log(this)
at the top of every function or method 😉I joke but I've seen code like that before (commented out thankfully)
Lol, thank you for your comment. I absolutely agree that
console.log(this)
is the easiest way to determinethis
😂 You don't even need to remember those rules.But fundamentals will always be useful for us 💪
(Personally, I use a preprocessor to insert console.log(this) into every function)! Here, the exclamation mark means NOT
Nice tip, but how do you write classes without this? The book was written in 2008, hopefully things have changed since then.
I totally agree that the inventors of Javascript had not their best day when they implemented "this", but in some cases it seems we cannot avoid this, and earlier or later you will get in trouble with this.
Hy all,
sometimes the JS-implementation of this is real strange. In callback functions, this is bound to the caller, not the object that defined the function. I found this very helpful to makes things more consistent:
This is used in the constructor:
This function is part of my DML-framework too...
Well, we should not start a discussion about functional programming here. Like Dave Farley said: you can write bad code in every language and with any paradigm... But interesting to see, that it is possible to avoid "this". Thank you for that.
Nice article—some good tips. I should point out, though, that
softBind
is not actually built-in. It's not on Function prototype likebind
is.oh yes, you are right! Thank you very much, I will edit my article.