A super-easy way to determine 'this' in JavaScript
Tags: javascript, webdev, beginners, react
Published: 2021-05-24 01:58:54

`this` is one of the fundamental concepts in JavaScript but it's also one of the most confusing concepts to wrap your head around as well. In this blog, I want to share with you the ways that I use to determine *what* `this` *is*.

Before going to deep-dive into all the specific rules that can be applied to determine `this`, you can remember an easy rule that can be true in **most** (not all the time) **cases**. This is how I remember it: 

- `this` bound to **object** when the function is a **method** of an object.
- `this` bound to **global object** or undefined when the function is **not a method**.

You can try to think about these 2 rules when you are going through all the examples. 

Rules for binding `this` : 

### Default binding

In this rule, we will consider the most common case when calling a function: *standalone function invocation*. 

Consider this code: 

```jsx
function foo() {
	console.log(this.a)
}

var a = '2' // If we declare var in global scope => there will be a property with the same name in the global object. 

foo() // 2 => Foo is called within the global scope 
```

In this example `foo` is called within the global scope so `this` will be binded to the global object. 

**Note:** this rule does not apply in `'use strict'`. 

### Implicit binding

Another rule is: does the call-site have a context object. 

Consider: 

```jsx
function foo() {
	console.log(this.a)
}

const object = {
	a: 42,
	foo: foo
}

object.foo() // 42
```

So `foo` is a method of `object` then the *implicit binding* rule says that `this` should be binded to the `object`.  

Only the top/last level object matters to the call-site (where the function is called): 

```jsx
function foo() {
	console.log( this.a );
}

var obj2 = {
	a: 42,
	foo: foo // for stays in obj2 => obj2 will be the call-site for foo
};

var obj1 = {
	a: 2,
	obj2: obj2
};

obj1.obj2.foo(); // 42
```

 **Implicit lost**

Whenever we pass our function as a callback function, we will lose the binding of `this`, which usually means it fallbacks to the default binding (*global object* or *undefined*). 

```jsx
function foo() {
	console.log( this.a );
}

var obj = {
	a: 2,
	foo: foo
};

var a = "oops, global"; // `a` also property on global object

setTimeout( obj.foo, 100 ); // "oops, global"
```

In this example, foo is passed as a callback so `this` will bound to the call-site where `setTimeout` is called. 

Or: 

```jsx
function foo() {
	console.log( this.a );
}

var obj = {
	a: 2,
	foo: foo
};

var bar = obj.foo; // function reference/alias!

var a = "oops, global"; // `a` also property on global object

bar(); // "oops, global"
```

In this example, `bar` is pointing to the `foo` function, so when we call `bar()` the call-site will depend on where `bar` is called, which is the global object in this example.  

### Explicit binding

Use `call` and `apply` 

Consider: 

```jsx
function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

foo.call( obj ); // 2
```

The differences between these two are ***"C for comma, A for array"**,*  which means that you can do: 

```jsx
foo.call(obj, arg1, arg2, arg3)

foo.apply(obj, [arg1, arg2, arg3])
```

**Hard Binding**

The implicit lost problem can be solved by doing this, called hard binding

```jsx
function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

var bar = function() {
	foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// `bar` hard binds `foo`'s `this` to `obj`
// so that it cannot be overriden
bar.call( window ); // 2
```

This is such a common pattern, it's provided with built-in util in ES5: `Function.prototype.bind`

```jsx
function foo() {
	console.log( this.a );
}

var obj = {
	a: 2
};

var bar = foo.bind(obj)
bar() // 2
```

In ES6, functions provide an optional parameter called "context" which is a work-around for people not to use `bind()` : 

```jsx
function foo(el) {
	console.log( el, this.id );
}

var obj = {
	id: "awesome"
};

// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome  2 awesome  3 awesome
```

### `new` binding

Consider: 

```jsx
function foo(a) {
	this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2
```

By calling `foo(..)` with `new` in front of it, we've constructed a new object and set that new object as the `this` for the call of foo(..).

### Determining `this`

1. Is the function called with `new` (**new binding**)? If so, `this` is the newly constructed object.
    `var bar = new foo()`

2. Is the function called with `call` or `apply` (**explicit binding**), even hidden inside a `bind` *hard binding*? If so, `this` is the explicitly specified object.
    `var bar = foo.call( obj2 )`

3. Is the function called with a context (**implicit binding**), otherwise known as an owning or containing object? If so, `this` is *that* context object.
    `var bar = obj1.foo()`

4. Otherwise, default the `this` (**default binding**). If in `strict mode`, pick `undefined`, otherwise pick the `global` object.
    `var bar = foo()`

### Exceptions

**Ignore** `this`

If we pass `null` or `undefined` to `call`, `apply` or `bind` , those values are effectively ignored, and the *default binding* rule will be applied here. 

```jsx
function foo() {
	console.log( this.a );
}

var a = 2;

foo.call( null ); // 2
```

**Note**: to be safe in case you want to bind this against the function call which comes from a library or a framework, and that function does make a `this` reference. You can accidentally point `this` to the *global object.* 

**Safer** `this` 

Instead of passing in a `null` we can pass in an empty object by doing `Object.create(null)`

You may wonder what the differences are between `{}` and `Object.create(null)`? 

`{}`: has the `Object.prototype`. 

`Object.create(null)` is really an empty object, it has nothing so it's considered to be cleaner. 

**Softening binding**

So if you remember hard binding, it's not really flexible as it only points to the specified obj 

```jsx
const foo = bar.bind(obj) // this always bounds to obj
```

We can construct an alternative utility that works similarly to `bind()` called `softBind()`. 

```jsx
if (!Function.prototype.softBind) {
	Function.prototype.softBind = function(obj) {
		var fn = this,
			curried = [].slice.call( arguments, 1 ),
			bound = function bound() {
				return fn.apply(
					(!this ||
						(typeof window !== "undefined" &&
							this === window) ||
						(typeof global !== "undefined" &&
							this === global)
					) ? obj : this,
					curried.concat.apply( curried, arguments )
				);
			};
		bound.prototype = Object.create( fn.prototype );
		return bound;
	};
}
```

The way I remember it is `softBind(obj)` only fallbacks to the `obj` if the *default* `this` is *global object.* 

Let's see the usage of `softBind()`

```jsx
function foo() {
   console.log("name: " + this.name);
}

var obj = { name: "obj" },
    obj2 = { name: "obj2" },
    obj3 = { name: "obj3" };

var fooOBJ = foo.softBind( obj );

fooOBJ(); // name: obj

obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2   <---- look!!!

fooOBJ.call( obj3 ); // name: obj3   <---- look!

setTimeout( obj2.foo, 10 ); // name: obj   <---- falls back to soft-binding
```

**Lexical** `this` 

Consider: 

```jsx
function foo() {
	setTimeout(() => {
		// `this` here is lexically adopted from `foo()`
		console.log( this.a );
	},100);
}

var obj = {
	a: 2
};

foo.call( obj ); // 2 
```

When you are using the *arrow function,* that function will bound to whatever `foo`'s `this` is at its call-time. 

### Summary

There are 4 rules to determine this: 

- Using `new`? Use the newly constructed object
- Using `call`, `apply`, `bind`? Use the specified object
- Method of an object? Use that object
- Default: global object and undefined in strict mode.

In most cases, you can just remember: 
- `this` bound to **object** when the function is a **method**
- `this` bound to **global object** or undefined when the function is **not a method**.

P/s: If you want to read more post about JavaScript or React, please visit my website: https://kelvinnguyen97.com/blog