The mysterious this
keyword in Vueland
12th May 2021 • 12 min read — by Aleksandar Trpkovski
If you are new to Vue.JS you might have realised that this
keyword is used everywhere. Some people might think that this
keyword is part of the Vue.JS framework itself. Others may wonder why this
keyword behaves a little differently in JavaScript, as compared to other languages such as Java, C++, PHP, etc. If you've experience an error that reads this is undefined
, you are not alone. In this article we will take a closer look at this common problem in Vue.JS, and how to solve it.
Before we dive into Vue.JS, we need to understand what exactly is this
keyword.
this
has been part of the JavaScript language for a very long time. To give you a time reference, Internet Explorer version 4.0 had fully supported this
keyword. Inspite of it being with us for a very long period of time, people still struggle to understand it. So how do we define this
? In most cases, the value of this
is determined by how a function is called in JavaScript.
The two types of functions in JavaScript
In Javascript we have two different types of functions, regular functions and arrow functions. They operate in almost the same manner, with one exception — they treat this
variable differently.
Example of a Regular function
var sum = function (a, b) {
return a + b;
};
Example of an Arrow function
var sum = (a, b) => a + b;
Even though both functions above do exactly the same job, a difference is noted when we start using this
keyword in the function.
Regular function
var person = {
firstName: “Aleks”,
lastName: “Trpkovski”
thisInRegularFunction() {
console.log("My full name is " + this.firstName + “ “ + this.lastName);
}
};
person.thisInRegularFunction(); // Output: My full name is Aleks Trpkovski
Arrow function
var person = {
firstName: “Aleks”,
lastName: “Trpkovski”
thisInArrowFunction:() => {
console.log("My full name is " + this.firstName + “ “ + this.lastName);
}
};
person.thisInArrowFunction; // Output: My full name is
From the examples above, this demonstrates the point of arrow functions not having their own this
. In order to fully understand the behaviour of this
keyword in both regular and arrow functions, please review the following reasoning.
this
in Regular functions
this
in regular JavaScript functions refers to the object that the function belongs to. In other words the value of this
depends on how the function is called, not where the function was declared. Even though the function was declared in a specific file or a particular object, this
changes its value based on the owner to the function call. It is important to remember the same function can have different owners in different scenarios.
The value of this
in regular JavaScript function is determined by 4 rules.
1. Default Binding
Default Binding happens when a function is invoked without any of these other 3 rules. In this rule this
points to the global object. This means that if you are in the browser, this
will be the window object. Default binding is applied for standalone functions. For example, any functions that are called without a “.” before it.
obj.foo(); // Not Standalone function
foo(); // Standalone function, Default Binding applies
Examples for Default Binding
function foo() {
console.log(“My name is “ + this.name);
}
var name = “Aleks”;
foo(); // Output: My name is Aleks.
2. Implicit Binding
Implicit Binding happens when invoked with a ”.” before it. For instance, obj.foo()
. In this rule, whatever is to the left of the dot becomes the context for this
in the function.
obj.foo(); // the value of `this` in foo is obj
obj1.obj2.foo(); // the value of `this` in foo is obj2
obj1.obj2.obj3.foo(); // the value of `this` in foo is obj3
Examples for Implicit Binding
function foo() {
console.log(“My name is “ + this.name);
}
var obj = {
name: “Aleks”,
foo: foo
};
obj.foo(); // Output: My name is Aleks.
One of the common frustrations you would face when Implicitly Binding a function, is that in certain circumstances, the function loses this
binding. This means that it will usually fall back to the Default Binding. This often happens with nested functions or when creating a reference to the function to a new variable.
2.1. Implicit Binding with Nested Functions
When a function is nested inside a method of an object, this
variable depends on the inner function invocation.
var obj = {
name: “Aleks”,
outer: function() {
function inner() {
console.log(“My name is “ + this.name);
}
inner(); // Default Binding applies
},
};
var name = “Nicole”;
obj.outer(); // Output: My name is Nicole
In the example above, although the outer function was called using implicit binding, the inner function was called using default binding. Thus, this
points to the global object.
2.2. Implicit Binding with a reference to the function
function foo() {
console.log(“My name is “ + this.name);
}
var obj = {
name: “Aleks”,
foo: foo
};
var name: “Nicole”;
var bar = obj.foo();
bar(); // Output: My name is Nicole.
Although bar appears to be reference to obj.foo()
, bar()
directly refers to foo()
. Hence, default binding applies.
3. Explicit Binding
Explicit binding of this
happens when one of the three .call()
, .apply()
, or .bind()
are used in a function. In that way we can force a function to use a certain object as this
. For instance, when calling foo.call(obj)
, the value of this
in the function foo
becomes obj
. Call, apply and bind do the same thing, with some little differences.
.call()
: Pass in the required object (value ofthis
) as the first parameter, along with additional parameters that are separated by comma. For examplefoo.call(obj, param1, param2, …)
..apply
: Is almost the same as.call()
with only difference in the way the actual parameters are passed. Unlikely Call, Apply accepts parameters as an array. For examplefoo.apply(obj, [param1, param2, …])
.
function foo(age, city) {
console.log(“My full name is “ + this.firstName + “ ” + this.lastName + “ ” + age + “ years old, living in ” + city);
}
var obj = {
firstName: “Aleks”,
lastName: “Trpkovski”
}
var age= 32, city = “Melbourne”;
foo.call(obj, age, city); // Output: My full name is Aleks Trpkovski 32 years old, living in Melbourne
foo.apply(obj, [age, city]); // Output: My full name is Aleks Trpkovski 32 years old, living in Melbourne
.bind()
: Is a little bit different than Call and Apply. When call a function with Bind, returns new function of the same name.
function foo(age, city) {
console.log(“My full name is “ + this.firstName + “ ” + this.lastName + “ ” + age + “ years old, living in ” + city);
}
var obj = {
firstName: “Aleks”,
lastName: “Trpkovski”
}
var age= 32, city = “Melbourne”;
var bar = foo.bind(obj, age, city)
bar(); // Output: My full name is Aleks Trpkovski 32 years old, living in Melbourne
4. new
Binding
When a function is invoked using the new
operator, also known as a constructor call, we create a brand new empty object and set that new object as this
inside the function.
function foo(name) {
this.name = “My name is “ + name;
}
var bar = new foo(“Aleks”);
console.log(bar.name); // Output: My name is Aleks
Arrow functions
ES6 introduced a special kind of function known as arrow functions. Unlike the regular functions where the value of this
is determined by the 4 rules mentioned above, the arrow functions use this
from the outer function or the global scope in which it is declared. For example, if the outer function is an arrow function, then it checks for the next outer function and repeats till the global scope.
function foo(){
var bar = () => {
console.log(this);
};
bar();
}
var obj1 = {
name: “Aleks”,
foo: foo
};
var obj2 = {
name: “”Nicole
};
foo(); // Output: Window {}
obj1.foo(); // {name: “Aleks”, foo: ƒ}
foo.call(obj2); // {name: “Nicole”}
As we can see from the example above, each time bar
is called, the value of this
is taken from the outer function. In this case, foo
.
var foo = () => {
console.log(this);
};
var obj1 = {
foo: foo,
bar: () => {
console.log(this);
},
};
var obj2 = {};
foo(); // Output: Window {}
obj1.foo(); // Output: Window {}
obj1.bar(); // Output: Window {}
foo.call(obj2); // Output: Window {}
var obj3 = new foo(); // Output: Window {}
As we can see in the last example above, none of the 4 binding rules has any direct impact on arrow functions.
Now when this
make sense, it brings us to the next section — how all this applies to Vue.JS.
Understanding this
keyword in Vue.JS
In this blog post, we won't focus on the fundamentals of Vue.JS framework. That would be going off track from the main purpose of this post. We will instead look at how this
keyword affects the function declaration in the method property.
As seen previously, we have two types of functions in JavaScript. So technically we can use either regular or arrow to declare a function in Vue.JS.
methods: {
regularFunction() {
console.log(this); // Output: Vue componet
}
}
methods: {
arrowFunction: () => {
console.log(this); // Output: this is undefined
};
}
Even though both function declarations are correct, there is a difference in how they treat this
variable.
In a regular function, this
refers to the owner of the function. In our case, this
refers to the Vue component. Hence, we can safely use this
to get any of the data properties, computed properties or methods of the Vue component.
On the other hand, in an arrow function, this
does not refer to the Vue component. As explained previously, the arrow functions use this
from the outer function or the global scope in which it is declared. In our example above that is the global scope and the reasoning behind of getting this is undefined
printed in the console.
It is recommended to use a regular function with Vue, especially when creating methods, computed properties, watched properties. But in certain scenarios, arrow functions come in very handy as well. We will have a look at various issues and how to solve them with either regular or arrow functions in the next few examples below.
Consider this code:
data() {
return {
name: "Aleks",
}
},
methods: {
foo() {
console.log("My name is: " + this.name); // Output: My name is Aleks.
// `this` on this line, refers to the Vue Component.
// we can use `this` to get any of the data properties of this Vue Component.
var bar = function() {
console.log("My name is: " + this.name); // Output: this is undefined
}; // this here refers to the Window object.
// the Default Binding applies
setTimeout(bar(), 100);
},
}
Where most likely we will get into trouble using this
, is when we declare another function inside the current function, as shown in the previous example. We have explained this scenario in the Implicit Binding section under Implicit Binding with Nested Functions.
This is a common problem in Vue.JS, especially when dealing with callbacks. this
in the bar
refers to the global object (the Window). As explained in the previous section in JavaScript, when you declare a new regular function, that function has it's own this
variable, which is different from the outer function, in our case, foo
in which it is declared. The confusing part in the example above is the Default Binding in the second function.
Lets refactor the provided built-in function setTimeout()
in JavaScript.
function SetTimeout(bar(), delay) {
// wait (somehow) for ‘delay’ milliseconds
bar(); // Standalone function, Default Binding applies
}
How can we fix this
There are several ways to deal with this
inside a function in Vue.JS.
One solution would be to use Explicit Binding with one of the three .call()
, .apply()
, or .bind()
methods on the function call.
setTimeout(bar.call(this), 100); // Output: My name is Aleks.
setTimeout(bar.apply(this), 100); // Output: My name is Aleks.
setTimeout(bar.bind(this), 100); // Output: My name is Aleks.
The other solution is most commonly used — by using a closure.
data() {
return {
name: "Aleks",
}
},
methods: {
foo() {
var self = this; // In the variable self we save a reference to the this
console.log("My name is: " + this.name); // Output: My name is Aleks.
// `this` on this line, refers to the Vue Component.
var bar = function() {
console.log("My name is: " + self.name); // Output: My name is Aleks.
}; // this here refers to the Window.
// we can use the reference we declared self which is refering to the Vue Component.
setTimeout(bar(), 100);
},
}
And the last, and by far most common solution these days is to use an arrow function.
data() {
return {
name: "Aleks",
}
},
methods: {
foo() {
console.log("My name is: " + this.name); // Output: My name is Aleks.
// `this` on this line, refers to the Vue Component.
var bar = () => {
console.log("My name is: " + this.name); // Output: My name is Aleks.
}; // this here also refers to the Vue Component.
// the value of `this` is taken from the outer function, in this case ‘foo’.
setTimeout(bar(), 100);
},
}
I hope this make sense!
Conclusion
- The value of
this
is determined by how a function is called in JavaScript. - In Javascript, we have two different types of functions, regular functions and arrow functions. They operate in almost the same manner, with one exception where they treat
this
variable differently. - The value of
this
in regular JavaScript function is determined by 4 rules: Default Binding, Implicit Binding, Explicit Binding andnew
Binding. - Unlike the regular functions where the value of
this
is determined by the 4 rules, the arrow functions usesthis
from the outer function, or the global scope in which it is declared. - The most challenging part when working with
this
in Vue.JS, is when we declare a function inside the current function. There are several ways to fix that.