1

I'm trying to understand at which point object member function becomes bound to object instance. In the following example

function F() {
  this.foo = 'foo';
};

var f = new F();
f.test = function() {
  alert(this.foo);
};

var c = f.test;


f.test();
(f.test)();
var d;
(d = f.test)();
c();    

output is "foo", "foo", undefined, undefined ( as expected ). If we look at AST for last 4 lines, it looks like this, and is the same MemberExpression argument for direct call and assignment + call

{
  "type": "MemberExpression",
  "computed": false,
  "object": {
      "type": "Identifier",
      "name": "f"
  },
  "property": {
      "type": "Identifier",
      "name": "test"
  }
}

So the question is: why can't we read (f.test)() as two distinct operations - MemberExpression + CallExpression and instead it looks more like a special case in the language "MemberCallEcpression" ( and why AST parsers don't have it as special case )? Why result of f.foo expression is bound function for function call and unbound for assignment?

Andrey Sidorov
  • 24,905
  • 4
  • 62
  • 75
  • It really is MemberExpression and CallExpression - why do you think it to be otherwise? – Pointy Sep 17 '15 at 04:07
  • Also, you question really isn't terribly clear; you say that the output you get is expected. The question of how "we read" the code isn't clear. What does that mean? What difference does it make how we read it? – Pointy Sep 17 '15 at 04:09
  • because result of MemberExpression is different based on what is _outside_ of the expression – Andrey Sidorov Sep 17 '15 at 04:14
  • 1
    The [*grouping operator*](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-grouping-operator-runtime-semantics-evaluation) doesn't call *GetValue* when evaluating the expression, so it effectively returns the expression and the base object is maintained, so *this* can be set in the call. – RobG Sep 17 '15 at 04:42

1 Answers1

0

Parentheses act as syntactic grouping mechanisms, but they don't do anything in the semantic interpretation/evaluation of expressions. Thus, in a parenthesized expression, whatever is inside the parentheses is held at the point just before the value is retrieved from the subexpression. That means that a property reference subexpression remains in the proper state such that when the value is finally extracted, the operation happens as if the parentheses hadn't been there at all.

edit — as RobG notes in a couple of comments, the key is in the way the language definition defines the process of evaluating an expression. The spec distinguishes between the steps involved in evaluating to the point at which it's possible to get an actual value to use with an operator, and the step of actually getting the value. In those terms, the ( ) tokens are not actually operators, while something like ., [ ], and = are. Thus, in order to engage in carrying out the behavior of an operator, that GetValue conceptual operation has to be done. At that point, if the source of the value is the result of an . or [], that fact is known and the "method call" this behavior will happen. The value itself however doesn't have any "memory" of where it came from. If the value is used to carry out the behavior of something like = (assignment) or is used as a function call parameter, then future operations on that value won't "remember" that the value was obtained from an object property reference.

It's all kind-of weird, I freely admit.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • This is definitely surprising behavior to me. I've always assumed that an expression returns a value/reference and parens merely force something into an expression context. I'd have thought that it would have bound `this` to the global object and return `undefined`. Is this behavior specified or implied anywhere in the spec? – slebetman Sep 17 '15 at 04:25
  • 1
    In the spec for [*function calls*](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-function-calls) you can see that *MemberExpression* is evaluated by first calling [*GetValue*](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-getvalue), which in turn resolves the *base* Object. The [*grouping operator*](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-grouping-operator-runtime-semantics-evaluation) doesn't call *GetValue*, it returns the expression. In an assignment, *GetValue* is called and only the reference is returned, so the base object is lost. – RobG Sep 17 '15 at 04:36
  • So having lost the base object, *this* defaults to global/undefined depending on strict mode or not. – RobG Sep 17 '15 at 04:37
  • so my understanding is that rhs part of `c = f.foo` is same reference as argument for CallExpression `(f.foo)`, but that reference itself is aware of the `f` object - see IsPropertyReference in [reference spec](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-reference-specification-type). – Andrey Sidorov Sep 17 '15 at 04:43
  • it's probably very different question, but would be good to know is it possible at language level, given property reference to get back reference to it's parent object – Andrey Sidorov Sep 17 '15 at 04:47
  • 1
    @AndreySidorov—the property value is a reference to an object, the value itself doesn't know which properties are holding it so once passed to another variable or property, there's no reference to where it came from. It would get quite complicated if a function was assigned to different objects, then those references assigned to other objects, which one should be used as the base? That's what *bind* is for. ;-) – RobG Sep 17 '15 at 07:27
  • Rob, "function call" spec answered my question - could you move comment to an answer? – Andrey Sidorov Sep 18 '15 at 01:13