2

I'm not sure if I'm doing this properly. Please have a look at this simple Vue component Test.vue:

<template>
    <div>
        Hi from {{name}}
    </div>
</template>

<script>
    let self;

    export default {
        created: function () {
            self = this;
        },
        methods: {
            load() {
                ajax().then((obj) => self.name = obj.data);
            }
        },
        data() {
            return {
                name: 'one',
            }
        }
    }
</script>

As you can see I'm saving the reference of this into a variable called self because the value of this changes in lambda functions, e.g. ajax().then((obj) => self.name = obj.data);

My problem is when this another instance of component is created, it overwrites the value of self in previous instance. So for example if I have two <test id="1"></test> and <test id="2"></test> then the later component overwrites the self variable of the first one (same happens in v-for too).

So my question is how can I create the self variable which saves to the value of this for every instance and doesn't get overwritten?

Edit: Yes I know I can do self = this inside every function but this is just a bare-bones example with 1 method. In my actual component I have 20+ functions I don't want to do self = this in every function. Which is why I can to create a variable that I can just assign once during the create call and use it everywhere (like we used to use that variable).

supersan
  • 5,671
  • 3
  • 45
  • 64
  • Why not do `let self = this;` just before the `ajax()` call? What you're experiencing is an issue with scoping. By placing the variable declaration within the scope of `load()`, you avoid overwriting the value with additional assignment operations. – B. Fleming Mar 21 '18 at 22:32
  • 1
    You can simple use this.name. I thought arrow functions are meant for that purpose. It takes the scope of the containing block – karthick Mar 21 '18 at 22:36
  • @karthick, indeed `this` should work where self is used in the example. – Richard Matsen Mar 21 '18 at 22:42
  • 1
    The reason why `let self` is getting over-written is that it's created globally. If you wish it to be per-instance, move it to the `data` property of the component. – Richard Matsen Mar 21 '18 at 22:45
  • It's true that `this` changes in a lambda. But you're not using any lambda, you're using the `arrow function`. And in arrow function, `this` does not change since an arrow function doesn't have its own `this`. Therefore, your entire effort is.. pointless. – N.B. Mar 21 '18 at 23:08
  • @N.B. a lambda function is just an [anonymous function](https://stackoverflow.com/questions/16501/what-is-a-lambda-function) or an expression who's value is a function. Arrow functions can be (and often are) lambda functions. In OP's case, it's a lambda function. – Bert Mar 22 '18 at 00:33
  • I'm very tempted to [close this as a duplicate](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) because all the answers are there, but will leave open because OP is attempting to use a closure, which would work, but defining `self` in the wrong place so it fails. – Bert Mar 22 '18 at 00:37
  • @B.Fleming please see my edit. I have a lot of functions (this was just a simple example). I don't want to repeat it over and over. Also the value of `this` changes when I use it in the fat-arrow functions, esp when using `.then` call (I often get this.load not defined which i can easily fix using the `self`, i.e. this is pointing to something else) – supersan Mar 22 '18 at 01:35
  • Where you likely got into trouble was defining *methods* (or lifecycle handlers, or computed properties) with arrow functions. [You can't do *that* with Vue](https://stackoverflow.com/questions/43929650/vuejs-why-is-this-undefined) because then `this` will refer to the enclosing scope (usually undefined in ES6). You want to use arrow functions when defining callback functions (as you do for `then` handers) *inside* methods, lifecycle handlers, etc. – Bert Mar 22 '18 at 01:53
  • @N.B. You said "you're not using any lambda, you're using the arrow function". That's wrong. OPs arrow function is a lambda. – Bert Mar 22 '18 at 10:16
  • 1
    @Bert so am I correct in thinking that you're saying that I said that an arrow function used here **was not a lambda**? Because, I wrote that he is not using JUST ANY lambda, his lambda is an ARROW function, and an arrow function hasn't got its own `this`. I see how it might have gotten about the wrong way. – N.B. Mar 22 '18 at 10:40
  • @N.B. Yes, that is how I interpreted what you wrote. Your clarification absolutely helps! – Bert Mar 22 '18 at 10:51
  • @Bert yea, I should probably pay more attention to attributes when relaying what I meant, when I re-read what I wrote I figured I missed the word `just` :) thanks for the pointer though! – N.B. Mar 22 '18 at 11:00

1 Answers1

4

What you are attempting to do is mostly unnecessary.

It is true that the value of this can be confusing at times in JavaScript. It is also true, though, that this is a well known problem, with well known solutions.

And these solutions, at least in problems related to the Vue instance, are:

But don't trust me just yet, let's go through an example. Take your source code:

    methods: {
        load() {
            ajax().then((obj) => self.name = obj.data);
        }
    },

As argument for the .then() you must pass a function. The value of this inside such function will depend on how you pass it.

In the first case (first solution from the two solutions above), one should use arrow functions (which you did). So, in that point of your code, the self is unnecessary because the this inside the arrow function still will point to the Vue instance.

    methods: {
        load() {
            console.log(this.name);
            ajax().then((obj) => this.name = obj.data);
        }
    },

In the example above, both this.name refer to the same property. See demo below.

const ajax = () => {
 return fetch('https://api.myjson.com/bins/18gqg9')
     .then((response) => response.json())
};

new Vue({
  el: '#app',
  data: {
  name: 'Alice'
  },
  methods: {
    yoo() {
      console.log('outside:', this.name);
      ajax().then((obj) => { this.name = obj.name; console.log('inside arrow, after change:', this.name); });
    }
  }
})
<script src="https://unpkg.com/vue@latest/dist/vue.min.js"></script>
<div id="app">
  <p>
    name: {{ name }}
  </p>
  <button @click="yoo">AJAX!</button>
  
  <p>Click the button above and check the console. The printed name variable is the same.</p>
</div>

Now, in the second solution, you would use a regular (non-arrow) function. But to make sure the this is kept, you would use .bind(this), as follows:

    methods: {
        load() {
            console.log(this.name);
            ajax().then(function (obj) { this.name = obj.data }.bind(this));
        }
    },

Similar to the previous case, in both places this.name refers to the same property. See demo below.

const ajax = () => {
 return fetch('https://api.myjson.com/bins/18gqg9')
     .then((response) => response.json())
};

new Vue({
  el: '#app',
  data: {
  name: 'Alice'
  },
  methods: {
    yoo() {
      console.log('outside:', this.name);
      ajax().then(function(obj) { this.name = obj.name; console.log('inside arrow, after change:', this.name); }.bind(this));
    }
  }
})
<script src="https://unpkg.com/vue@latest/dist/vue.min.js"></script>
<div id="app">
  <p>
    name: {{ name }}
  </p>
  <button @click="yoo">AJAX!</button>
  
  <p>Click the button above and check the console. The printed name variable is the same.</p>
</div>

So, as you can see, within the Vue instance, the declaration of such self variable is unnecessary.

acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • You are right, I tried the code and it seems to work. But there have been many instances where I get an error like `this.load` is not defined, but when I save `this` in `self` and use that it works. Let me try to find that code sample. I've seen that `this` sometimes point to something else and it the reason why I had to come up with this whole saving `this` into another variable thing. – supersan Mar 22 '18 at 01:38
  • Sure, find it out and show us. You probably are doing something like `ajax().then(this.someMethod);` in this case, you *have* to use `.bind()`, like:`ajax().then(this.someMethod.bind(this));` – acdcjunior Mar 22 '18 at 02:05
  • Yes, you are absolutely right. It was `ajax().then(this.load)`. I now see why it is wrong to do that without bind (even though if I'm not wrong, `ajax().then(self.load)` seems to work). Thanks for the help. – supersan Mar 22 '18 at 02:06
  • You don't *have* to use bind in `ajax().then(this.someMethod)`. Methods are already bound to Vue and cannot be re-bound. [Here is an example](https://codepen.io/Kradek/pen/jzmGOZ?editors=1010). – Bert Mar 22 '18 at 02:11
  • Indeed they are! – acdcjunior Mar 22 '18 at 02:16
  • 1
    Anyway, using arrow functions or `.bind()` is the standard way of handling the `this`. – acdcjunior Mar 22 '18 at 02:19
  • @Bert, @acdcjunior thanks for explaining it to me and I now understand it correctly. I somehow misunderstood the arrow function changes the `this` operator which is of course incorrect. I don't see the need to use `self` anymore. – supersan Mar 22 '18 at 02:38