Function context in JavaScript
Tags: this, javascript, context, functional
Published: 2022-03-22 06:22:22

### What is context?
What happens when you drop a ball?. If your answer is "the ball will hit the floor", you assumed this happened on Earth (or any place with gravity 😀). What if this happened on Moon?. The answer will be different. Right?. This is because the context is different. So context can be defined as surrounding data that will affect whatever is inside.

### Functions and Context
Functions are a block of code that solves a specific problem and functions can be executed anywhere in the program (i.e., you can call it whenever you need it). Functions are never executed in isolation, they will always run in a context. Look at the code below.
```js
var name = "John Doe"
function printName() {
  console.log(name);
}
printName();
```
##### Output
```bash
John Doe
```
Function `printName` can access variable `name` just because the context of the function is `the global` and the variable is defined on `the global`.

> I called context as `the global` because when you are running on Node it will be `global` and on browser, it will be `window`.

The context of the function will determine what variables or methods a function can access. This can be determined statically by just looking at the code. If there is nothing unusual and if the code is predictable there is nothing to worry about right?. Unless there is `this`.


### What is `this` in JavaScript?
The concept of `this` is not entirely exclusive to JavaScript. All programming languages have it. Then what's the hustle about this in JavaScript?. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3eu2km1bnjlum49dk6bu.png)

Let's take a look at this code below.
```js
var name = "John Doe";
function printName() {
  console.log(this.name);
}
setTimeout(printName, 1000);
```
##### Output - Node
```sh
undefined
```
##### Output - Browser
```sh
John Doe
```
> The output may differ in different browsers as well.

The output in the `Browser` differs from `Node` because the implementation of `setTimeout` in both environments is different.

## Why?
Functions in JavaScript are very powerful. Unlike other programming languages, functions in JavaScript can act differently. Like in the above code, the output is different because the value of `this` is bonded at the time of execution, which makes it unpredictable, yet JavaScript does this way. 

Let's take a look at another piece of code.
```js
var obj = {
  name: "John Doe",
};
obj.printName = function () {
  console.log(this.name);
};
obj.printName();
```
##### Output
```bash
John Doe
```
In the above code, the context of the function `printName` is `obj`. Since the value of `this` is bonded dynamically, you can access `name` from the `this` keyword. In here, a function is attached later on the `obj`, this is possible only if the `this` keyword value is bonded dynamically. You can't achieve this on programming languages such as Java or Python.

## The Problem
Take a look at the code below.
```js
var name = "John Doe 1";
var obj = {
  name: "John Doe 2",
};

function printName() {
  console.log(this.name);
}

obj.printName = printName;
obj.printName();
printName();
```
##### Output - Node
```sh
John Doe 2
undefined
```
##### Output - Browser
```sh
John Doe 2
John Doe 1
```
In the above code snippet, we are using the same function in both cases, we are not passing any parameters or anything. But the output is different since the value of `this` is dependent on the context. So when you see a code like this.
```js
function printName() {
  console.log(this.name);
}
```
You can't determine the output, since it depends on the context. Getting different outputs for the same function is not a good idea, but if you use it correctly, you can do magic with it.
## Playing with `this`
#### Strict mode
If you are defining a function like this
```js
var name = "John Doe"
function printName() {
  console.log(this.name);
}
```
What will be the context of the function?. It will bond to `the global`, But if you are running in `strict mode`, the context will be `undefined`.
#### `new` keyword
In JavaScript, you can invoke a function using the `new` keyword. In this case, the value of `this` will be an empty object.
```js
function printName() {
  console.log(this);
}
new printName();
```
##### Output:
```bash
{}
```
### `bind`, `call` and `apply`
Since the context of the function is hard to determine, JavaScript provides some methods on the function to pass context with it.
#### call/apply
`call` and `apply` invokes the function immediately with a given context and arguments. The only difference is how the function arguments passed.  In the `apply` function arguments are passed as an array and in the `call` function arguments are passed comma separated.
```js
var obj = {
  number: 1,
  multiply: function (number1, number2) {
    console.log(this.number * number1 * number2);
  },
};
obj.multiply.call({ number: 2 }, 3, 4); //arguments as comma separated
obj.multiply.apply({ number: 2 }, [3, 4]); // arguments as an array
```
##### Output
```sh
24
24
```
Here the context is changed in the first argument of both `call` and `apply`, which makes the output `24` instead of `12`.
#### bind
`bind` is another method available on function, which will return a new function with a given context.
```js
var obj = {
  number: 1,
  multiply: function (number1, number2) {
    console.log(this.number * number1 * number2);
  },
};
var multiply = obj.multiply.bind({ number: 2 });
multiply(3, 4);
```
##### Ouput
```sh
24
```
## Arrow functions
Arrow functions are introduced in ES6 to solve this context issue. `this` keyword is not bonded in the arrow function.
```js
var obj = {
  name: "John Doe",
  printName: function () {
    setTimeout(function(){
      console.log(this.name);
    }, 1000);
  },
};
obj.printName();
```
##### Output - Node
```sh
undefined
```
##### Output - Browser
```sh
""
```
Here, the context of the function is `setTimeout`, so the value will depend on that. To solve this:
```js
var obj = {
  name: "John Doe",
  printName: function () {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  },
};
obj.printName();
```
##### Output - Node/Browser:
```sh
John Doe
```
There will be no context binding on arrow functions, which makes the output of the code more predictable.
## Conclusion
Everything in JavaScript works the same as almost every other programming language except the `this` keyword. The value of the `this` keyword is determined at the run time. This may make your code unpredictable, but you can achieve almost everything using `this`.