1

I'm implementing a JavaScript interpreter, and I can't figure out the details of binding functions to objects in JavaScript.

A rudimentary example:

const o = {
  x: 1,
  getX: function() {
    return this.x;
  }
};
o.getX(); // returns 1

The tricky part is what happens when you assign getX to a variable:

let gX = o.getX;
gX(); // returns undefined

My question is: how does the runtime know that o.getX() gets bound to o, but gX() should be unbound? I would assume gX and o.getX are pointing to the exact same function!

I first thought that maybe the presence of the . is what makes the difference. So, in the grammar, there's a production like <method-call> ::= <expr> '.' ID '(' <expr>* ')', and this expression is treated differently (the result of evaluating the first <expr> is bound to the function found under ID) than a "regular" call (without a .).

But the following expression seems to disprove this theory, because (o.getX)() also returns 1. However, in some magical way, (gX = o.getX)() returns undefined, even though it's clear to me the assignment expression returns its right-hand size, so o.getX in this case!

Is there a simple explanation of how these semantics are implemented? I can't figure out a way that my runtime is supposed to differentiate that o.getX is bound to o, but gX, even though it's pointing at o.getX, is not bound.

Adam Ruka
  • 136
  • 4
  • 2
    When talking about functions defined with `function` keyword, a simple rule is, that `this` is _always_ bound when the function is _called_, without exceptions (sometimes it's a bit hidden where exactly a function is actually called). See [the standard](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object-objects) for more details. – Teemu Jan 26 '23 at 07:47
  • 1
    *"My question is: how does the runtime know that `o.getX()` gets bound to `o`, but `gX()` should be unbound? ... I first thought that maybe the presence of the `.` is what makes the difference."* Not the `.` per se (it works with bracket notation as well), but yes, the fact that the function call is happening on the immediate result of a property access operation is what tells the JavaScript engine what value to use as `this` when calling the function. Some of the answers to the linked questions go into the technical detail, but: If we start with ... – T.J. Crowder Jan 26 '23 at 08:17
  • 1
    ... [property accessors](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors-runtime-semantics-evaluation), you can see that the result (via [EvaluatePropertyAccessWithIdentifierKey](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-evaluate-property-access-with-identifier-key)) of *MemberExpression `.` IdentifierName* (for example) is a [Reference Record](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-reference-record-specification-type) that ... – T.J. Crowder Jan 26 '23 at 08:18
  • 1
    ... includes the object resulting from *MemberExpression* as its [[Base]] value. That Reference Record is used when doing the [function call](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-function-calls-runtime-semantics-evaluation) and eventually in [EvaluateCall](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-evaluatecall), where it gets the *thisValue* to use from the Reference Record's [[Base]]. ... – T.J. Crowder Jan 26 '23 at 08:18
  • 1
    ... When just doing `gX()`, there is no property expression, and the *ref* used with EvaluateCall isn't a Reference Record, so the *thisValue* used is `undefined`. (The `[[Call]]` internal method of the function may convert `undefined` to `globalThis` if it's a loose-mode function; and of course, arrow functions ignore the `this` they're called with — but those are different topics.) – T.J. Crowder Jan 26 '23 at 08:18

0 Answers0