36
var a = 1;
var b = {
  a : 2,
  c : function () {
    console.log(this.a);
  }
};

b.c(); // logs 2
(b.c)(); // logs 2
(0, b.c)(); // logs 1

The first is understandable, for "this" is pointed to Object "b". But why does the second one log the same result? I thought "this" should be pointed to the global execution context. And the third one, it seems that the comma operator influences the execution context.

Ziqi
  • 423
  • 3
  • 8
  • `this` is not an "execution context." – T.J. Crowder Mar 18 '16 at 08:53
  • 1
    @Crowder Could you please explain more clearly? What I find in [ECMAScript® Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-10.3) is "The value associated with the **this** keyword within ECMAScript code associated with this execution context." – Ziqi Mar 18 '16 at 10:32
  • 1
    Execution contexts *have* a `this`, but the `this` is not the execution context, it's a property of it. More specifically, the *`this` binding* is a property of the execution context's *lexical environment* (or the `this` binding of an enclosing context's lexical environment, if the execution context's lexical environment doesn't have one, as happens with ES2015 arrow functions). [§8.3](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-execution-contexts) is a good place to start. Warning: The 5th edition spec was turgid, but the 2015 one is even worse. :-) – T.J. Crowder Mar 18 '16 at 11:14
  • possible duplicate of [What's the reason for using such syntax: `(0, _.Em)()`](http://stackoverflow.com/q/9735424/1048572) – Bergi Sep 16 '16 at 12:29
  • Possible duplicate of [What's the reason for using such syntax (0, \_.Em)();](http://stackoverflow.com/questions/9735424/whats-the-reason-for-using-such-syntax-0-em) – Grundy Mar 22 '17 at 12:54

2 Answers2

22

You really have a nice corner case there! My take on it:

  • the first is straightforward. Just a standard call. The '.' operator lets you call the function setting b as the execution context.
  • the second is exactly the same thing: the parens are entirely optional and the interpreter is treating the expression inside it as a bound function call. Actually I didn't expect this: I thought the interpreter would be going to reset this to the global object, but actually it's keeping it linked. Probably just so "casual" language users do not freak out.
  • the third one is more standard (at least for those who live in JavaScript land): as soon as your function is passed on in an expression (in this case by the , operator) the this value is lost. This is because b.c is a Property Reference (deep rabbit hole details in the specification, here, courtesy of T.J.Crowder). So, you are actually passing around the function itself, no more bound to the declaring object. So when you call it this is going to be passed in as the global object.

See it this way: (object.function)() gets simplyfied into object.function(), because the enclosing parens are completely optional; (0, object.function)() is parsed as (expression yielding a function)() which is going to lose the object binding to this, because function is already unbound.

Really nice example!

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53
  • 3
    Good update, although *"as soon as your function is passed on in an expression (in this case by the , operator) the this value bound to the execution context is lost"* is still incorrect: We're not using `this`, we're using `b`, and the mechanism by which the object reference doesn't end up being used is because the property reference isn't directly used to make the call. Details in [§12.3.4.1](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function-calls-runtime-semantics-evaluation). – T.J. Crowder Mar 18 '16 at 12:36
18

Refer to Indirect eval call, which gives more details about it.

 (     0        ,          b.c   )        (  )
     |____|   |_____|    |_____|
     Literal  Operator   Identifier

     |_________________________|
     Expression

  |______________________________|
  PrimaryExpression

  |______________________________|        |________|
  MemberExpression                        Arguments

  |________________________________________________|

  CallExpression

We can use the comma operator to fashion an indirect call to b.c which will force it to execute in the global context, the value of a is 1 in the global context.

Also the result of (b.c = b.c)() is 1

> (b.c = b.c)()
  1

Speaking in terms of ECMAScript, this is because both — comma operator (in (0, b.c) example) and = operator (in (b.c = b.c) example) perform GetValue on its operands.

Other indirect call formats as below

> (b.c, b.c)()
  1 
> (1? b.c: 0)()
  1
> (__ = b.c)()
  1
zangw
  • 43,869
  • 19
  • 177
  • 214