1

I'm following along YDKJS Scope & Closures, specifically the Appendix C - Lexical this section where the following code is presented:

var obj = {
    id: "awesome",
    cool: function coolFn() {
        console.log(this.id);
    }
};
var id = "not awesome";
obj.cool(); // awesome
setTimeout(obj.cool, 100); // not awesome (because this refers to window and window and it will take the global id)

Then he proposes the following solution (the first of a couple that he offers):

var obj = {
    count: 0,
    cool: function coolFn() {
        var self = this;
        console.log(self); // This will still be window if invoked from setTimeout
        if (self.count < 10) {
            setTimeout(function timer() {
                self.count++;
                console.log("awesome?");
            }, 100);
        }
    }
}
obj.cool(); // awesome?

The idea is that you bind this to a lexically-scoped variable called self so we can sort of abstract away the confusing behavior of this, and then he goes on to explain why there are better solutions (for instance, just understanding how this works, which is the topic of the next book in his series). However, for this particular offered solution, I don't see how it solves anything? If we call setTimeout(obj.cool, 500); outside of obj, won't the same problem arise as in the first example? obj.cool() will run and set self equal to the global window object and break. Am I missing something? What is the point of declaring a lexically-scoped self variable if its definition depends on the state of this. For instance, if we use setTimeout, then self definitely doesn't refer to "self". I've also noticed that (at least in the environment I work in), you don't even need to state the var self = this line, you can just use the word "self" out of the box with the same effect.

Edit: To make the question more precise, let's consider the following snippet:

var obj = {
    id: "awesome",
    cool: function coolFn() {
        var self = this;
        console.log(self.id);
    }
};
var id = "not awesome";
obj.cool(); // awesome
setTimeout(obj.cool, 100);

This snippet is identical to snippet 1 except that it uses the var self = this mechanism and logs self.id instead. However, it has the exact same output as the first snippet. What problem is this solving if it doesn't solve the problem of correcting the output?

univuniv1
  • 63
  • 5
  • 1
    _“Why is var self = this proposed as a solution […]”_ — Because that suggestion was before [arrow functions](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) were introduced. Even `.bind(this)` is a more straight-forward solution… see [How does the “this” keyword work?](/q/3127429/4642212). – Sebastian Simon Jun 21 '21 at 20:23
  • @SebastianSimon No, that's not what I'm saying. I'm saying I don't understand how it's even a solution at all _to the setTimeout example that he mentions in the book_ – univuniv1 Jun 21 '21 at 20:24
  • @univuniv1 have you tested it to see whether it works at least? – Pointy Jun 21 '21 at 20:25
  • @Pointy Yes I have, it doesn't work. Using setTimeout(obj.cool, 1000) just sets self to equal the window object and then the same problem occurs. – univuniv1 Jun 21 '21 at 20:26
  • Yes, `var self = this` won’t work in `setTimeout(obj.cool)`, because passing a method to the `setTimeout` function only passes the value, not the reference; therefore the method `cool` is detached from its context. See [Losing “this” context in JavaScript when passing around members](/q/30486345/4642212). I doubt that `var self = this;` is actually proposed as a solution to _this_ particular problem. Or it’s an error in the book. – Sebastian Simon Jun 21 '21 at 20:27
  • 1
    @SebastianSimon Okay, so correct me if I'm wrong. It seems to me what you're saying is that there are two problems with the first snippet. The first is using _this_ directly in obj.cool is a bad idea because _this_ can refer to something other than the intended object. This is the problem that using self solves. self will always refer to the value of this that was stored when the assignment happened. But another problem that using self doesn't solve is that it can't control what gets assigned to self in the first place. So self may very well refer to window or something which isn't itself. – univuniv1 Jun 21 '21 at 20:39
  • 2
    "*If we call `setTimeout(obj.cool, 500);` outside of obj, won't the same problem arise as in the first example?*" - yes. It's really weird that he moved the `setTimeout` call inside the `cool` method, making the two codes hard to compare. – Bergi Jun 21 '21 at 20:42
  • @Bergi yeah, so I went ahead and copied the same code as the first snippet with the only difference being the var self = this statement, and logging self.id instead of this.id, and the same problem occurs! – univuniv1 Jun 21 '21 at 20:45
  • Found the section: [Appendix C: Lexical-this](//gist.github.com/cmdoptesc/474d6a9575bc2ad483f8b553fc02255f). _“The problem is the loss of `this` binding on the `cool()` function. There are various ways to address that problem, but one often-repeated solution is `var self = this;`.”_ — What a strange paragraph. Context loss isn’t solved by `self`. The author starts out with the `setTimeout(obj.cool)` problem, but then goes on to talk about the solution of a completely different problem. Unless _the `.bind` method_ is supposed to be the solution here: `setTimeout(obj.cool.bind(obj))` works. – Sebastian Simon Jun 21 '21 at 20:57
  • @SebastianSimon Okay, thank goodness someone else found this quite jarring. – univuniv1 Jun 21 '21 at 20:59
  • For completeness note that there's absolutely nothing special about the variable name "self". You could use "banana" instead and it would work exactly the same way. – Pointy Jun 21 '21 at 21:10
  • @Pointy I understand, and I've heard it often called _that_, but _self_ has the particular characteristic that it's misleading if it gets assigned with something that isn't the containing object. Furthermore, _self_ is also unique in that it's not technically a reserved word, but in many modern environments you can use it even without doing the var self = this; assignment. This functionality must have been added because _self_ was used so frequently for this task. – univuniv1 Jun 21 '21 at 21:17
  • Yes, that's all true, but it's *very important* to keep in mind that there is *nothing* special about the name "self". It's just an identifier, and it has absolutely no special meaning as far as the runtime is concerned. – Pointy Jun 21 '21 at 21:21
  • I agree, and the onus is on us to name identifiers in a way that faithfully represents what they identify. As a result, it's important for variable names to be precise and descriptive, especially when their purpose has a high chance of being misunderstood. It should be obvious, but I think worth mentioning that no one in their right mind should name a boolean "array1". On the other hand, if a variable's name is _self_, it's reasonable to expect that it should refer to itself, otherwise, choose a different name. – univuniv1 Jun 21 '21 at 21:31
  • And then you'll find productive code with three or four nested callbacks, and then you'll ask, _what the heck is `self`_? ;) – Jonas Wilms Jun 21 '21 at 21:41
  • @JonasWilms Well if it identifies _this_, then of course it's _that_. If in some imagined scenario, it really was too difficult to even _know_ what self refers to, then I'd argue that it's better to name it something like "bananas"; at least this way it doesn't have the potential to be misleading. – univuniv1 Jun 21 '21 at 22:33

1 Answers1

3

What is the point of declaring a lexically-scoped self variable if its definition depends on the state of this.

It does not. It depends on the state of this when the assignment self = this happens. At that point the function was invoked with obj.cool(), thus this is bound to obj and as such self will be bound to the same object too.

If we call setTimeout(obj.cool, 500); outside of obj, won't the same problem arise as in the first example?

Yes, exactly. Admittedly the example is a bit weird, the same concept could be demonstrated with

 setTimeout(function() {
    obj.cool();
 });

Here obj is the lexically bound reference to the context, and during the call obj.cool(), this will also correctly refer to obj. Now for multiple timeouts this might get a bit repeptitive and it can be moved into a method:

 var obj = {
    cool: function() { /*...*/ },
    runCoolSoon: function() {
      var self = this;
      setTimeout(function () {
        self.cool();
      });
   },
 };

 obj.runCoolSoon();

That example is very close to the given example.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Yes, I understand and that's what I meant. However, in the setTimeout example, when the var self = this executes, _this_ is equal to the window object (that's not self). The second snippet is proposed as a solution to the first snippet, but how is it a solution at all if _this_ still refers to the window object when the var self = this statement executes? – univuniv1 Jun 21 '21 at 20:33
  • 1
    @univuniv1 I tried to build some bridges, however I do agree that the original example is a strange choice ... – Jonas Wilms Jun 21 '21 at 21:17