Regular Functions VS Arrow Functions
Let me now show you some pitfalls of the "this" keyword related to regular functions and arrow functions—this way, we can learn when to use and avoid each of them.
To start, let's get back to the john
object from before, and let's also add a firstName
property and a second method called greet
, but this time using an arrow function.
const john = {
firstName: 'John', year: 1991,
calcAge: function () {
console.log(this);
console.log(2025 - this.year);
},
greet: () => console.log(`Hey ${this.firstName}`),};
Now, let's call the greet method and check the result in our browser's console.
// JS Code
john.greet()
From above, you can see that we get, Hey undefined
. So that's not Hey John
, as we might have expected. The reason is precisely the one that I mentioned in the last lecture, which is the fact that an arrow function does not get its own this keyword. It will simply use the this keyword from its surroundings. In other words, its parents this keyword, and in this case, the parent scope of the greet
method is the global scope.
Just note here that the curly braces which we use to create our objects is actually not a code block. You might think that it is and that it creates its own scope, but it doesn't. Again, it is not a code block. It is an object literal. It's just a way that we literally define objects.
So everything inside those curly braces belongs to the global scope, including the greet
method. This, therefore, means that the arrow function greet
, which does not have its own this keyword, will use the this keyword from the global scope. Remember that it is the window
object.
Knowing that, this means that we will get undefined
if we also try to log this.firstName
in the global scope.
// JS Code
john.greet()
console.log(this)console.log(this.firstName)
I think that this is actually something that we haven't seen before. So note that when we try to access a property that doesn't exist on a certain object, we do not get an error but simply undefined
.
This behavior can become pretty dangerous if we use var
to declare variables. Remember from a previous lecture that variables declared with var
actually create properties on the global object.
Let's imagine we have something like this.
var firstName = 'Mary';
// JS Code
john.greet()
console.log(this)
console.log(this.firstName)
Now, as we run the greet
function, we will have Hey Mary
in our console.
We have this result because inside the greet
function, the this keyword is window
which you can check by logging the value of this in the greet
function.
So again, window
is the this keyword inside of the greet
arrow function, even though the john
object called that arrow function. That rule does not apply here, because again, it's an arrow function.
If we take a look at the window
object in the console, we will see that it does indeed have a property called firstName
with the value of Mary
. And again, that's because if we declare variables with var
, that creates these kinds of properties on the global object.
And so; therefore, this.firstName
, which translates to window.firstName
, is then Mary
. That's why we then get Hey Mary
in the console. That's yet another reason not to use var. You can see that there's a pretty long list against var
. So from this example, the big takeaway is that you should never use an arrow function as a method as a best practice.
In my opinion, that's even true if you're not using the this keyword in a particular method because if you have this rule of never using an arrow function as a method, then you never have to think about which type of function you should use. You will always just use a normal function expression, and like this, you will prevent this kind of mistake from happening.
So the mistake Hey Mary
is easy to prevent by simply not using var
, and the bug Hey undefined
would have easily been avoided by just using a regular function.
If we had this:
const john = {
firstName: 'John',
year: 1991,
calcAge: function () {
console.log(this);
console.log(2025 - this.year);
},
greet: function () { console.log(this) console.log(`Hey ${this.firstName}`); },};
john.greet();
Then, of course, the object john
calling the function would then be the this keyword because now the greet
method does get its own this keyword.
One final example of a pitfall of the this keyword is when we have a function inside of a method.
Let's enhance our calcAge
method by adding a simple function that will return if the person is a millennial.
const john = {
firstName: 'John',
year: 1991,
calcAge: function () {
console.log(this);
console.log(2025 - this.year);
const isMillenial = function () { console.log(this.year >= 1981 && this.year <= 1996); }; isMillenial(); },
greet: () => {
console.log(this);
console.log(`Hey ${this.firstName}`);
},
};
john.calcAge();
Let's now see what happens as we reload the page.
From above, we can see that we get an error saying we cannot read property year
of undefined
. That error comes from the below line.
const isMillenial = function () {
console.log(this.year >= 1981 && this.year <= 1996);};
What this means is that the this keyword must be undefined
. That's what the error says, and we can simply check that by logging the value of this inside our isMillenial
function.
Now the question is, why is that? Why are we having undefined
? Well, if we think about this, isMillenial()
is just a regular function call. It is a regular function call, even though it happens inside a method, and the rule says that inside a regular function call, the this keyword must be undefined
. And so, therefore, it is undefined inside our isMillenial
function.
This is just as if this function was outside of the calcAge
method. You will notice that you will get the same result if you copy the isMillenial
function out of the john
object and execute it.
Ssome people consider that this is a bug in JavaScript, but in my opinion, it's not really. It's just how the this keyword works. It's a clear rule that a regular function call has the this keyword set to undefined
, and that's just what is happening here.
There are two solutions to this problem. The first solution is to use an extra variable that we usually call self
.
const john = {
firstName: 'John',
year: 1991,
calcAge: function () {
console.log(this);
console.log(2025 - this.year);
const self = this; // self or that const isMillenial = function () {
/**
* Through the scope chain, self will be equal to this.
*
* The self variable is referenced in the isMillenial
* function but it's not of course in the scope.
*
* JavaScript therefore goes up in the scope chain, into the parent scope
* where self is defined, and it is defined as the this keyword.
*
* And so this is a way in which we can preserve the this keyword.
*/
console.log(self); console.log(self.year >= 1981 && self.year <= 1996); };
isMillenial();
},
greet: () => {
console.log(this);
console.log(`Hey ${this.firstName}`);
},
};
john.calcAge();
This was kind of the pre ES6 solution, but you might still find this solution in some older codebases. However, now in ES6, we have a more modern and better solution. The solution is to use an arrow function that does not need any extra variable.
The arrow function solution works because, if you remember, arrow functions do not have their own this keyword. The arrow function will simply use the this keyword of its parent scope, and in this case, that will be the calcAge
method.
const john = {
firstName: 'John',
year: 1991,
calcAge: function () {
console.log(this);
console.log(2025 - this.year);
const isMillenial = () => { console.log(self); console.log(self.year >= 1981 && self.year <= 1996); };
isMillenial();
},
greet: () => {
console.log(this);
console.log(`Hey ${this.firstName}`);
},
};
john.calcAge();
If we now try to run it in the browser, everything will work as expected.
From this example, you see that it's pretty important to know exactly how each of the different functions works regarding the this keyword because then you can use them according to your specific needs.
I also want to touch on the arguments keyword to finish this lecture quickly. We learned in the lecture about the execution context and call stack that functions also get access to an arguments keyword.
Like the this keyword, the arguments keyword is only available in regular functions. Let's starts by writing two functions: one arrow and one function expression that add two number.
const addFuncExp = function (a, b) {
console.log(arguments); return a + b;
};
addFuncExp(4, 3);
const addArrowFunc = (a, b) => a + b;
Now, if you reload your browser, you will see the arguments keyword in the console, which is basically an array with 4
and 3
. That's exactly the two parameters that we passed in.
This can be useful when we need a function to accept more parameters than we actually specified. This is something that we have never done before. Up until this point, we have only ever specified exactly the number of arguments that are functions required. That is, two parameters, and so two arguments.
But it is completely legal to add more arguments. They will not have a name because we didn't name them, but they exist.
addFuncExp(4, 3, 1, 7, 8);
And we can see them above in the arguments array. We can use them, for example, in a loop and add all the numbers together.
But now, my point here is that the arrow function does not get this keyword. If we try to log the arguments keyword, we get an error saying arguments is not defined.
const addArrowFunc = (a, b) => {
console.log(arguments); return a + b;
};
addArrowFunc(4, 3);
This was simply just to show you that the arguments keyword exists, but only in regular functions. That is, in function expressions and function declarations.
The arguments keyword is not that important in modern JavaScript anymore because now we have a more modern way of dealing with multiple parameters. But still, it's important that you are aware that this arguments keyword exists.
With all these lectures on regular and arrow functions, you should know all the differences between them and, even more importantly, when to use each of them.