0

Let's say we have an object with a function property, and this function uses this:

let o = {
  a: function() {
    return `a, ${this.b}`;
  },
  b: 'b'
};

I understand that we'll see 'a, b' for the following piece of code:

let o = { a: function() { return `a, ${this.b}`; }, b: 'b' };
console.log(o.a());

This is because javascript considers the in-place "topology" of o.a(...) to mean that o ought to be set as this within this occurence of o.a's execution.

I also understand that the following produces 'a, undefined':

let o = { a: function() { return `a, ${this.b}`; }, b: 'b' };
let f = o.a;
console.log(f());

This is because the "topology" of f(...) tells javascript that there is no explicit this.

For this reason I was surprised that the following produces 'a, b':

let o = { a: function() { return `a, ${this.b}`; }, b: 'b' };
console.log((o.a)());

It looks to me like using parentheses around (o.a) should force javascript to resolve o.a to an unbound, this-less function, before going any further.

For completeness, this example successfully unbinds the instance of the function call:

let o = { a: function() { return `a, ${this.b}`; }, b: 'b' };
let f = () => 'f';
console.log((o.a || f)());

Even though the || short-circuits on the first operand, there's still a difference between (o.a) and (o.a || f).

  • How should I comprehend javascript's logic to determine the value of this?
  • Are there any other unintuitive examples related to these?
  • Is there any better or more official vocabulary I can use to describe what's going on here?

Thanks!

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • 3
    `(o.a)` doesn't really do anything. The function `a` still belongs to the object `o` – Taplar Jan 02 '20 at 22:47
  • 1
    `(o.a)` === `o.a` - the two are literally the same, just like `1 + (2)` is no different in any way to `1 + 2` – VLAZ Jan 02 '20 at 22:49
  • Well, I could also say that for `let f = ;`, `f` is always exactly equivalent to `` - but in this case it isn't, as the `this` context disappears – Gershom Maes Jan 02 '20 at 22:50
  • 3
    The exact semantics are all explained in soporific detail in the spec, if you want to really get the full story. – Pointy Jan 02 '20 at 22:50
  • 1
    @GershomsupportsMonica I don't think you got it: `o.a()` is *literally* the same as `(o.a)()` - there is no difference between the two in any way shape or form. Your expectation is incorrect as `(o.a)` isn't going to lose the context. However, if you have a different expression like `(o.a || f)()` or `(0, o.a)()` that's not the same case as before. – VLAZ Jan 02 '20 at 22:55
  • The simple answer is that the context is retained whenever `o.a` is called directly, rather than being saved in a temporary or variable. Putting parentheses around it doesn't make it a temporary, but using it as part of an expression does. – Barmar Jan 02 '20 at 22:59
  • 1
    The canonical way to remove the context is `(1, o.a)()`. See https://stackoverflow.com/questions/5161502/indirect-function-call-in-javascript – Barmar Jan 02 '20 at 23:00
  • Ah, so it's almost like under certain conditions js will decide to treat parenthesized code as if it isn't parenthesized at all? (As opposed to "remembering that parenthesization happened, but also somehow keeping track of `this`-context for some duration"?) – Gershom Maes Jan 02 '20 at 23:02
  • As much as it may not seem like it at first, the value of `this` is actually fairly intuitive. The difference lies in the fact that when you assign functions to other variables you need to provide a context using `bind` or it will default to either `undefined` or `window`. a.e. `let f = o.a;` leads to an **undefined** context. `let f = o.a.bind( o );` leads to the context of **o** – zfrisch Jan 02 '20 at 23:04
  • @GershomsupportsMonica parentheses don't do the same thing every time. It depends on what's inside them, so JS doesn't "decide" that parentheses are irrelevant but rather that the expression inside will have different results based on the *expression itself*. Removing the parentheses in `1 + (2)` will have no effect but with `(1 + 2) * 3` there *will* be a difference. – VLAZ Jan 02 '20 at 23:05
  • 2
    You're thinking way too hard about parentheses. They're not magic. It's order of operations when parsing the code. Parentheses are evaluated first. – zfrisch Jan 02 '20 at 23:06
  • @zfrisch I guess that's the main thing I'm asking about - it really seems like `(o.a)` *isn't* evaluated first, as it resolves to a `this`-less function. – Gershom Maes Jan 03 '20 at 01:40
  • `(o.a)()` -> `o.a()` -> the method `a` is executed with `o` as `this`. – VLAZ Jan 03 '20 at 18:45
  • If we do `let ts = 'abc'.toString`, then calling `ts()` is *not* the same as calling `'abc'.toString()`! There is a difference based on whether the js parser can find a `this` value inline. To me it is strange that `('abc'.toString)()` behaves like `'abc'.toString()`, and not `ts`, as the parenthesization requires us to resolve the value of `'abc'.toString` before performing a function call on the result. It seems like the parentheses are not resolved first, though they should have precedence. – Gershom Maes Jun 05 '20 at 19:32

0 Answers0