Tuesday, 30 March, 2021 UTC


Summary

Scope, this and arrow functions are complex concepts that are not easily grasped, especially when you're getting started with JavaScript and Angular. Let’s take a look at them together!
What Does Scope Mean in JavaScript?
It’s hard to talk about what this means in a scope if we do not first define what a scope actually means.
Remember that popular scene in The Lion King where the big cat tells the smol cat that everything the light touches will be his kingdom? He was actually making a reference to the scope of what his land encompasses.
Anything inside the scope of this land that is touched by the light is considered to be in scope. It is part of the reign. Anything outside of the land that is touched by the light, where the hyenas live, is out of scope.
The scope in JavaScript talk is where a variable, a function or an object lives and how it can be accessed. Some variables live in the land of the cats, some in the land of hyenas.
We have two types of scope in JavaScript—global and local. Let’s take a look at both of them in more detail.

Global Scope

Everything in the global scope can be accessed anywhere in your code, including the JavaScript environment itself. In our example, every animal that lives inside the land of light is in the global scope.
For example, when you are in the browser’s dev tools (right click, inspect element, open the console tab) and you write window.document, you see the entire HTML of the page you are on in the console. This is possible because the window object is global in the browser.
Global elements can be accessed anywhere without code, even if they are called or used within other functions or methods inside objects.
console.log(window) // Global can be "seen" here
var globalVar = 'HAI' // This is also global, its not within a fn, but at the root

function insideAFunction() {
  console.log(window, globalVar) // And it can be "seen" here


  var nestedFunction = function() {
    console.log(window, globalVar) // And even here
  }
}

Local Scope

Anything declared or “born” inside a function exists in the local scope of that function, which means any code outside of the function will be completely unaware of it.
When you think about local scope, think about the privacy of your own home—whatever happens inside of it is not known to the outside world (hopefully!), only to people living with it.
Now think of your neighborhood—it, in itself, has many houses, each with their own local scope, but the neighborhood itself is also a bigger area which has its own local scope, and so on until you reach the global limit.
Examine the following example:
function meUsesLocalScope() {
    var meIsLocal = 'Hi! I am new.';
    console.log(meIsLocal);
};

meUsesLocalScope(); // Prints 'Hi! I am new.'

// this will throw an error saying it's undefined because
// meIsLocal lives inside the scope of meUsesLocalScope, and is not globally available
console.log(meIsLocal);
Let’s look at this extended example, which mixes both scopes, because sometimes we need to use global elements in our functions:
var meIsGlobal = 'Hi! Everyone knows me. I will introduce you!';
  
function meUsesGlobalAndLocalScopeElements() {
  var meIsLocal = 'Hi! I am new.';
  console.log(meIsLocal); // Logs: 'Hi! I am new.'
  console.log(meIsGlobal); // Logs: Hi! Everyone knows me. I will introduce you!
};

meUsesGlobalAndLocalScopeElements();

// Outside the function, we are still on the global scope and this works
console.log(meIsGlobal); // Logs: Hi! Everyone knows me. I will introduce you!

// this will throw an error saying it's undefined because
// meIsLocal lives inside the scope of meUsesLocalScope, and is not globally available
console.log(meIsLocal);
A Quick Reminder on What This Is
The word this is a special keyword in JavaScript, which means it can be used anywhere in your code.
This is a reference to an element’s self. Think about the word me in the English language. Me in itself depends on who the speaker is, when I say me, I mean Marina Mosti. When you say me, it takes on a completely different context and meaning.
Just like in English, this takes the meaning of who uses it—generally speaking, the containing object or function that it is written in, with some exceptions.
How Do Scope and This Play Together?
Depending on where you call this from, it will give you different results. Terrible, I know —keep in mind the example of me.
Remember our example about the global scope in the browser? If you call this in the global scope, you will get the window object.
Printing this.document gives you the same result as window.document. Try it out in your browser!
Consider the following example:
function simpleMath() {

        function add(a, b) {
          const c = a + b;
          console.log( `a + b = ${c}` )
        }

  const a = 3;
  const b = 5;
  console.log( `a = ${a} and b = ${b}`)
  add( a, b );
}

// Both of these will work since we're on the global scope
simpleMath();
this.simpleMath();
This code will execute with no errors. But what if you tried to change add( a, b ) inside of the simpleMath function to this.add( a, b )?
Now the code throws an error. But why? Logic would indicate that this should point to the scope of simpleMath, and that add should be in that scope. However, in this case, this refers to the global scope. And the global scope does not know of a function named add.
If you are executing this code in the browser’s console, this is the window object. So if you console.log( this ) inside simpleMath, you will see the window object printed in the console—including the reference to simpleMath, since it itself is on the global scope.
Ok, let’s keep the same concept but write it a bit differently.
const math = {
  a: 3,
  b: 5,
  add: function() {
    const c = this.a + this.b;
    console.log( `a + b = ${c}` )
  },
  multiply: function() {
    const d = this.a * this.b;
    console.log( `a * b = ${d}` );
  },
  print: function() {
    console.log( `a = ${this.a} and b = ${this.b}`);
    this.add();
    this.multiply();
    console.log( this )
  }
}

math.print();
This time we created an object called math which holds all our math logic.
In this example, this refers to the local scope or the object math, so anything inside that object is in this. Objects behave like people: when a person says me, they mean themselves; when an object says this, it means itself.
Try console.log( this ) in any of the functions. You will see everything declared inside math printed in the console.
Now write console.log( this ) right after math.print(). You will see the window object again, because now this is called outside of math, which means it refers to the global scope.
There is one more thing that we need to be aware of when it comes to using this—arrow functions. Take a deep breath, and let’s tackle it together.
Arrow Functions and This
Arrow functions were introduced in ES6. It’s a shorthand way of writing a smol function.
Pre ES6:
let numbers = [ 10, 15, 20, 25, 30 ];
let largerThanFifteen = numbers.filter( function( number ) {
  return number > 15;
} );
Using arrow functions:
let numbers = [ 10, 15, 20, 25, 30 ];

let largerThanFifteen = numbers.filter( number => number > 15 )
If you need a refresher on the syntax, you can read more about arrow functions on MDN.
If you’re like me and prefer the eye candy of => over function, you need to be aware of a side effect that it may cause with scope and the this keyword.
The scope of an arrow function does not have a this of its own. It inherits the this from the enclosing scope. That is the most important thing you have to remember.
Let’s go back to our (simplified) math object.
const math = { 
  a: 3,
  b: 5,
  add: function() {
    let c = this.a + this.b;
    console.log( `a + b = ${c}` )
  },
  print: function() {
    console.log( `a = ${this.a} and b = ${this.b}`);
    this.add();
  }
}
math.print();
All good, it works as expected. Now let’s rewrite add with the arrow syntax.
const math = { 
  a: 3,
  b: 5,
  add: () => {
    let c = this.a + this.b;
    console.log( `a + b = ${c}` )
  },
  print: function() {
    console.log( `a = ${this.a} and b = ${this.b}`);
    this.add();
  }
}
math.print();
If you execute this code, you will get an error saying this is undefined in this line:
let c = this.a + this.b; // this is undefined
That’s because this is inherited from the enclosing scope, which in this case means the scope of the print function and only that scope.
If you call math.add() outside of the math object, then the this inside add is going to point to the global scope, and this.b will actually look for b inside window.b.
One of the possible solutions to keep our object structure would be in the code depicted below.
const math = { 
  a: 3,
  b: 5,
  add: ( a, b ) => a + b,
  print: function() {
    console.log( `a = ${this.a} and b = ${this.b}`);
    let c = this.add( this.a, this.b );
    console.log( c )
  }
}
We have to pass a and b as arguments to add. No more errors.
Wrapping up
Scope, this and arrow functions are complex concepts that are not easily grasped. Do not feel bad if it takes you a bit to wrap your head around them. Once you do, you'll unlock a lot of power in JavaScript and Angular!
The important thing is that you always keep in mind that if you are experiencing some sort of “odd” behavior inside an arrow function, a usual culprit and a good place to start debugging is to see if there is any misuse of the this keyword.
For more information regarding this, please refer to MDN’s this documentation page.
As always, thanks for reading and share with me your experiences on Twitter at: @marinamosti.
P.S. All hail the magical avocado!
P.P.S. ❤️☠️