9

A super-easy way to determine 'this' in JavaScript

 3 years ago
source link: https://dev.to/kelvin0712/a-super-easy-way-to-determine-this-in-javascript-ob5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Cover image for A super-easy way to determine 'this' in JavaScript

A super-easy way to determine 'this' in JavaScript

May 24

・5 min read

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:

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 
Enter fullscreen modeExit fullscreen mode

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:

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

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

object.foo() // 42
Enter fullscreen modeExit fullscreen mode

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):

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
Enter fullscreen modeExit fullscreen mode

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).

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"
Enter fullscreen modeExit fullscreen mode

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

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"
Enter fullscreen modeExit fullscreen mode

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:

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

var obj = {
    a: 2
};

foo.call( obj ); // 2
Enter fullscreen modeExit fullscreen mode

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

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

foo.apply(obj, [arg1, arg2, arg3])
Enter fullscreen modeExit fullscreen mode

Hard Binding

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

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
Enter fullscreen modeExit fullscreen mode

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

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

var obj = {
    a: 2
};

var bar = foo.bind(obj)
bar() // 2
Enter fullscreen modeExit fullscreen mode

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

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
Enter fullscreen modeExit fullscreen mode

new binding

Consider:

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

var bar = new foo( 2 );
console.log( bar.a ); // 2
Enter fullscreen modeExit fullscreen mode

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.

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

var a = 2;

foo.call( null ); // 2
Enter fullscreen modeExit fullscreen mode

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

const foo = bar.bind(obj) // this always bounds to obj
Enter fullscreen modeExit fullscreen mode

There is another built-in utility that works similarly to bind() called softBind().

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()

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
Enter fullscreen modeExit fullscreen mode

Lexical this

Consider:

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

var obj = {
    a: 2
};

foo.call( obj ); // 2 
Enter fullscreen modeExit fullscreen mode

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK