What is "this"? Why you should avoid arrow functions on Vue methods

js_bits_bill

JS Bits Bill

Posted on April 23, 2021

What is "this"? Why you should avoid arrow functions on Vue methods

this in Vue

Every Vue instance has an option for methods. This is simply an object whose properties are methods we'll use in our Vue app:

const app = Vue.createApp({
  data() {
    return { count: 4 };
  },
  methods: {
    increment() {
      // "this" will refer to the component instance
      this.count++;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Vue will bind the this keyword to the instance so that it will always reference the component instance. Because of this, it's really import to not use arrow functions when defining methods because they always bind this to the parent context, which is not actually the Vue instance - but the global object (the Window):

const app = Vue.createApp({
  data() {
    return { count: 4 };
  },
  methods: {
    increment: () => {
      // "this" will refer to the Window
      this.count++;
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Y Tho

The reason is that every regular (non-arrow) function defines its own this value, which always refers to the owner of the function it's in.

So in this example:

const person = {
  name: 'Ted',
  logName() { 
    console.log(this.name); // Ted
    console.log(this); // person object
  }
};

person.logName();
Enter fullscreen mode Exit fullscreen mode

this refers to the person object, which is logName's owner.

This is true even when inside a stand alone function:

function test() { console.log(this); }
test(); // Window is logged
Enter fullscreen mode Exit fullscreen mode

That's because the owner of test is the window object:

window.test; // test() { console.log('this', this); }
Enter fullscreen mode Exit fullscreen mode

There's a huge exception to this. Whenever this is used inside of a function within another method, its binding is lost and this will then refer to the global (window) object:

const obj = {
  func1() {
    console.log('func1 this', this); // "this" is obj
    (function func2() {
      // "this" binding is lost here
      console.log('func2 this', this); // "this" is Window
    })();
  }
};

obj.func1();
Enter fullscreen mode Exit fullscreen mode

This is considered somewhat of a bug in the JavaScript language since it's very quirky and trips up a lot of people.

When arrow functions were released in ES6 they provided a way to force this to automatically bind to the parent scope which produces a more expected outcome:

const obj = {
  func1() {
    console.log('func1 this', this); // "this" is obj
    (() => {
      console.log('func2 this', this); // "this" is obj
      // "this" was bound to func1's "this" reference
    })();
  }
};

obj.func1();
Enter fullscreen mode Exit fullscreen mode

The really important takeaway here is that arrow functions do not have their own this. When you use the this keyword inside an arrow function you're referring to the this of either a surrounding regular function/method or the global object if there is none.

Let's look at another example:

const person = {
  firstName: 'Bob',
  getName() {
    console.log(this.firstName);
  }
};

person.getName();// Bob
Enter fullscreen mode Exit fullscreen mode

person.getName is a regular old function. That means it has its own this reference - which we learned is the owner of the function - the person object.

So what happens when we make getName an arrow function?

const person = {
  firstName: 'Bob',
  getName: () => {
    console.log(this.firstName);
  }
};

person.getName(); // undefined
Enter fullscreen mode Exit fullscreen mode

this.firstName is undefined in this case. Why? Because the getName arrow function is binding the this keyword to the this of a surrounding regular function, which there is none - so the global object is what's bound to this. And window.firstName is of course undefined.

Tying it back to Vue

With this in mind, let's look back at a Vue instance object:

const app = Vue.createApp({
  data() {
    return {
      firstName: 'Bob'
    }
  },
  methods: {
    getName() {
      console.log(this.firstName); // Bob
    }
  },
  created() {
    this.getName();
  }
});
Enter fullscreen mode Exit fullscreen mode

this is being used inside a regular function and not arrow functions which means this is bound to an owner object. If we were to make getName an arrow function it would mean this becomes the global object like we saw in our previous examples.

It's important to note that when using regular functions, Vue does its own assignment of the this keyword to be the actual Vue instance - so the owner object is a little different than if we were using our own custom object. This under-the-hood mapping allows us to access data properties and methods like this.otherMethod and this.lastName which is convenient.

One last thing

While you should not use arrow functions to define methods, it's fine to use them inside your methods as the this keyword will bind to the correct parent reference.

const app = Vue.createApp({
  data() {
    return {
      checkmark: '',
      letters: ['a', 'b', 'c']
    }
  },
  methods: {
    processLetters() {

      // Using arrow functions inside processLetters is fine!
      const processedArray = this.letters.map(letter => {
        // "this" here binds to the "this" of processLetters
        return `${letter}-${this.checkmark}`
      });

      console.log(processedArray); // ["a-✔", "b-✔", "c-✔"]
    }
  },
  created() {
    this.processLetters();
  }
});
Enter fullscreen mode Exit fullscreen mode

Check out more #JSBits at my blog, jsbits-yo.com. Or follow me on Twitter!

💖 💪 🙅 🚩
js_bits_bill
JS Bits Bill

Posted on April 23, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related