“This” is one of the most important and commonly asked questions in javascript. Many times, due to a poor understanding of the concept, we tend to write and encounter bugs in our software.
In this article, we will try to demystify “this” topic. :)
To determine the binding of "this," we must first understand a few rules. So, before you jump to a conclusion, consider how the function you’re working with is invoked or called.
Let’s go ahead and list the necessary rules that will help us understand “This” and “Binding”
Type of “This” Binding
Default Binding:
When we call or invoke a function normally, default binding applies. For ‘default’ in the case of a browser “window” refer to this example:
function foo(){
console.log(this);
function bar(){
console.log(this);
}
bar()
}
foo() // both of the console statement will print the window.
//because , here functions are invoked normally.
So, no matter what the function is or where the function is defined , if it is being called normally, then default binding rules will apply.
Implicit Binding:
The second type of binding is “Implicit” Binding. Do you remember how it is sometimes when you call the function with (.) (dot)? like foo.bar(). This is called implicit binding.
In this case “this” refer to the calling object:
function foo(){
console.log(this)
}
const obj = {foo: foo}
obj.foo() // prints obj as this
Here “obj” is calling “foo”. So “This” is now referring to the calling obj.
function foo(){
console.log(this);
function bar(){
console.log(this);
}
bar()
}
const obj = {foo: foo}
obj.foo() // what do you think? function bar will print?
In this case “function foo” has one inner function function “bar”. and we are invoking the bar() inside the foo, so you can think that since “obj” is calling the foo, “bar” also has the same binding as foo, i.e the calling object.
But remember, what we have learned in point 1, that if a function is being called normally, no matter where it is defined and where it is being called, “this” will always refer to the “Default Binding”.
in this case, foo has “obj” as this, because obj is calling, but “bar” is still being invoked normally, so for bar(), this is still “window”.
The priority of Implicit Binding is higher than that of default Binding.
Explicit Binding
When we invoke any function using “call”, “apply” or “bind”, then it is explicit binding. Because here we are passing “this” explicitly.
function foo(){
console.log(this)
}
const obj = {name: 'Javascript'}
foo.call(obj) // here we are passing obj as "thisArg" so now foo has "obj" as
//this binding
If we pass “null” or “undefined” as thisArg in a call, apply or binding, it will start behaving like the Default Binding would, and “binding of this” would be ‘window’ in the case of browser env.
The priority of Explicit Binding is even higher than that of implicit binding.
For example:
function foo(){
console.log(this)
}
const obj = {name: 'Javascript', foo: foo}
const obj1 = {name: 'HackerNoon'}
obj.foo.call(obj1) // Here call has more precedence over (.) dot. so now foo has obj1
// as this binding
Since explicit binding has more priority than implicit binding, now “foo” has obj1 as “this”.
What do you think the output would be for the below example?
function foo(){
console.log(this)
}
const obj = {name: 'Javascript', foo: foo}
const obj1 = {name: 'HackerNoon'}
obj.foo.call(null)
Please remember the point we discussed. If “null” or “undefined” is passed as “thisArg” in call or apply or binding method, it starts behaving like the “Default Binding” so in this case “this” will refer to the window object.
New Binding
When we invoked any function with a “new” keyboard, many things happen under the hood.
-
A new blank object is created.
-
prototype of this object is linked to the prototype of the constructor function
-
the constructor function is excecuted, binding the “newly created” object as “this”
-
obj is returned, if nothing else is being returned
As we can see, when we invoke a function with “new” keyword, the function has a different obj from “this”
function Foo(bar1, bar2) {
this.bar1 = bar1;
this.bar2 = bar2;
console.log(this)
}
const obj = new Foo('bar1', 'bar2')
Here one object with “property” bar1 and bar2 is “this” binding for the function Foo.
The precedence of “new” Binding is even higher than “explicit” or “implicit” Binding.
For example:
function Foo(bar1, bar2){
this.bar1 = bar1;
this.bar2 = bar2;
console.log(this)
}
const obj = {foo: Foo}
new obj.foo('bar1', 'bar2')
//output : {bar1: 'nar1', bar2: 'bar2'}
Here we are using “new” keyword with “(.) ,i.e obj.foo() but still, “binding of this” is still the newly created object and not the “obj”.
Arrow function(ES6)
Arrow functions behave a little bit differently. The rules we have discussed so far do not work for Arrow functions.
Arrow functions do not have their own “this” binding, so they take “this” from the nearest environment, and if there is no such env. “this” always refers to “window” in the browser for an arrow function.
function foo(){
const es6 = () => console.log(this);
function normal(){console.log(this)};
es6();
normal();
}
foo()
In this case, both functions will print “window” as “this” because nothing special is happening.
Let’s change the way “foo” is being called and see what happens.
function foo(){
const es6 = () => console.log(this); // obj
function normal(){console.log(this)}; // window
es6();
normal();
}
const obj = {foo: foo}
obj.foo()
Now “foo” has “obj” as its implicit “this” binding, so “es6” will print “obj” as ‘this’ because as we discussed es6 function takes “nearest this”, but the “normal” function will still print “window” because “Rule of Default Binding” is still applied for the “normal” function.
So when deciding the “this” for the arrow function, just check to see if the outside environment has any “this” binding. If yes then that’s the “this” binding for an es6 function.
If not then “window” would be the answer in the browser context
const obj = {foo: () => console.log(this)}
obj.foo()
What would be the output?
Take a pause, the rule of implicit binding won’t work here, because foo is now an es6 function.Since there is no other binding, because foo is not inside any normal function which has its own “this”, the binding of “this” is “window” for now.