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.
functionkeyword, a simple rule is, thatthisis 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 for more details.o.getX()gets bound too, butgX()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 asthiswhen calling the function. Some of the answers to the linked questions go into the technical detail, but: If we start with ....IdentifierName (for example) is a Reference Record that ...gX(), there is no property expression, and the ref used with EvaluateCall isn't a Reference Record, so the thisValue used isundefined. (The[[Call]]internal method of the function may convertundefinedtoglobalThisif it's a loose-mode function; and of course, arrow functions ignore thethisthey're called with — but those are different topics.)