ES6 classes: deferring the creation step

# Claude Pache (10 years ago)

In the currently specced design of classes, the fact that the creation step and the initialisation step of built-in object may be separated by arbitrary user code is thought to be problematic.

Jason proposed a @@new behaviour in replacement of @@create that would avoid the issue 1. Here is a counter-proposal (or an improved proposal, ad libidum), which is not tightly coupled to @@new. In fact, it is designed to (well, TBH, it happens to) just work in absence of @@create or @@new hook, although it is possible to introduce them.

The basic idea is the following: the creation process (the @@create step) is deferred as late as possible. It will appear that, for built-in base classes like Array, creation occurs just before initialisation, making the created-but-initialised state of such objects unobservable, at least in absence of user-overridable @@create hook.

Non-Constructed Objects

Non-Constructed Objects are introduced in order to describe the state of not-yet-defined this-bindings.

Non-Constructed Objects is probably not be the greatest approach to spec the thing, especially when considering the awful Step 3 of InitializeThisBindings() algorithm below; it is just a convenient hack that allows me to expose the idea with minimal change from the current specification draft.

A Non-Constructed Object is a special placeholder exotic object with the following internal slots:

  • [[Constructor]], which holds a reference to the constructor;
  • [[NonConstructed]], set to true.

Non-Constructed Objects may only appear as value of this-bindings inside functions (or, using the spec language, as value of the thisValue component of a function environment record). Any attempt to get an explicit reference to such an object in user code will throw an error. The only way to pass (implicitely) a reference to a Non-Constructed Object between different function environment records, is through calls to super.

InitializeThisBindings(nonconstructedObj, obj) abstract operation

This operation performs the actual initialisation of the this-bindings that were previously deferred:

  1. Assert nonconstructedThisObj is a Non-Constructed Object.
  2. Assert obj is an ordinary object.
  3. Replace all references to nonconstructedThisObj with references to obj. (In particular, this step will effectively initialise the this-binding of every function environment record that used to reference nonconstructedObj.)

Additional runtime semantics of the this keyword

Any attempt to get an explicit reference to the thisValue of a function environment record while it holds a Non-Constructed Object, shall throw an error.

new C(...args)

When a constructor C is called with arguments args, the following steps are taken. In particular, the actual initialisation of the this-value is deferred.

  1. Let thisValue be a new Non-Constructed Object, with its internal slot [[Constructor]] set to C.
  2. Let R be the result of C.[[Call]](thisValue, args).
  3. NOTE. The operation InitializeThisBinding() may have been called during the previous step, meaning that thisValue may now be a regular object.
  4. ReturnIfAbrupt(R).
  5. If Type(R) is Object, return R.
  6. If thisValue is a Non-Constructed Object, throw an error.
  7. Return thisValue.

No more [[Construct]]

[[Construct]] internal method is gone. Actually it is conflated with the [[Call]] internal method, modified as below. The key fact is that [[Call]] is able to see if it should have a [[Construct]]-like behaviour, by examining whether its thisArgument is a Non-Constructed Object.

F.[[Call]] (thisArgument, argumentsList) for user-defined functions

User-defined functions has the currently specced [[Call]] behaviour Section 9.2.2, with the following additional step inserted somewhere near the beginning of the algorithm, e.g., after step 1:

1bis. If the thisArgument is a Non-Constructed Object, a. If F’s [[NeedsSuper]] internal slot is set to false (IOW, if F’s code doesn’t contain super), i. Let proto be the result of GetPrototypeFromConstructor(thisArgument.[[Constructor]], "%ObjectPrototype%"). ii. ReturnIfAbrupt(proto). iii. Let obj be ObjectCreate(proto). iv. Perform InitializeThisBindings(thisArgument, obj). v. Assert: Now, thisArgument is an ordinary object. b. Else, thisArgument is left untouched. // it is meant to be handled at the occasion of the enclosed super call.

F.[[Call]] (thisArgument, argumentsList) for bound functions

In the algorithm sepcced in Section 9.4.1.1, step 2 is replaced with:

  1. If the thisArgument is a Non-Constructed Object, let boundThis be thisArgument. // this is the current [[Construct]] behaviour 2bis. Else, let boundThis be the value of F’s [[BoundThis]] internal slot. // this is the current [[Call]] behaviour

F.[[Call]] (thisArgument, argumentsList) for the built-in Object constructor

There is no change: Object(...) acts as a factory rather than as a constructor, as currently specified, and the thisArgument is ignored. In particular trying to subclass Object will lead to unexpected results. Note however that new Object does still work.

F.[[Call]] (thisArgument, argumentsList) for the built-in Array contsructor

The [[ArrayInitializationState]] internal slot is gone, and Step 4 of the algorithms in Sections 22.1.1.* is replaced with (where O is the this-value):

  1. If O is a Non-Constructed Object, a. Let proto be the result of GetPrototypeFromConstructor(O.[[Constructor]], "%ArrayPrototype%"). b. ReturnIfAbrupt(proto) a. Let array be ArrayCreate(<<length>>, proto). b. Perform InitializeThisBindings(thisArgument, array)
  2. Else, etc.

The [[Call]] behaviour of other built-in constructors is left as an exercise to the reader.

Comments

There is a nice side-effect of the proposal: The new internal check intended to discriminate between call-as-function and call-as-constructor is easier and more robust. In particular,

  • hacks such as [[ArrayInitializationState]] are no longer needed;
  • bound functions are truly subclassable (see 2).

However, it remains very hard for user-defined functions to distinguish correctly between constructor/initialisation-calls and method/function-calls, or to write code that works well in both cases. (At least the situation is not worse than in ES5-.)

Optional: the @@create hook

A @@create hook can easily be placed as follows: In each [[Call]] internal methods defined above, a call to (thisArgument.[[Constructor]]).@@create could replace the steps spanning from GetPrototypeFromConstructor(...) inclusive to InitializeThisBindings(...) exclusive.

Whether such a hook is compatible with, e.g., DOM constructors, is left to the appreciation of the competent people. At worst, a built-in constructor could cheat by defining its own [[Call]] internal method that would refuse to run the @@create hook.

Optional: the @@new hook

Alternatively, the following hook may be installed: At the beginning of each call to F.[[Call]], the following steps are taken:

  1. if thisArgument is a Non-Constructed Object, a. If F has an own property named @@new, i. Let R be the result of F[@@new].[[Call]](thisArgument.[[Constructor]], argumentsList). ii. ReturnIfAbrupt(R). iii. If Type(R) is Object, α. InitializeThisBindings(thisArgument, R). iv. Return R. b. etc.
  2. etc.

Note that we don’t look for inherited @@new property, in order to preserve the initialise-at-latest-time behaviour.

—Claude

# Claude Pache (10 years ago)

Please do the following substitutions in my message:

  • "created-but-initialised" → "created-but-not-initialised"
  • "an ordinary object" → "an object (i.e. a value of type Object) that is not a Non-Constructed Object" (2x)

—Claude

Le 27 juin 2014 à 15:56, Claude Pache <claude.pache at gmail.com> a écrit :

# Brendan Eich (10 years ago)

I like it! Cc'ing others who may have missed it. Boris is DOM guru you seek.

Does it address the bound function issue you cited in the previous thread? It appears not to, but I might be missing something (jetlag).

# Brendan Eich (10 years ago)

Brendan Eich wrote:

I like it! Cc'ing others who may have missed it. Boris is DOM guru you seek.

Forgot to suggest a better spec-name: "Pre-constructed" instead of "Non-Constructed".

# Claude Pache (10 years ago)

Le 28 juin 2014 à 17:49, Brendan Eich <brendan at mozilla.org> a écrit :

I like it! Cc'ing others who may have missed it. Boris is DOM guru you seek.

Does it address the bound function issue you cited in the previous thread? It appears not to, but I might be missing something (jetlag).

It does address the issue (if my reasoning is correct).

Rethinking on the subject, I think that the intended semantics would have appeared more clearly, if I had kept a modified [[Construct]] internal method separate from the [[Call]] one. Then, the new operator would invoke [[Construct]], but, more notably, super would also invoke [[Construct]] when the this-binding is not yet initialised (i.e., when this is a Non-Constructed Object). — super invocations occurring when this is initialised regularly invoke [[Call]].

So, roughly, the first super in a constructor would invoke the super-constructor with the semantics of a constructor rather than of a method. Therefore, subclassing objects with even wildly different [[Construct]] and [[Call]] behaviours would just work.

Also, the @@new hook would just have been an exact override of the modified [[Construct]] internal method. (Which is another reason why I should have kept [[Construct]] and [[Call]] separate.)

# Domenic Denicola (10 years ago)

This looks nice to me as well. But given the length I had trouble internalizing in what externally-visible ways it would change the status quo, especially with the optional @@create addition. The writeup as-is is very spec-language-focused. Would it be simple to summarize that for us?

There's also the question of what implementers think. To me it seemed a little more "weird" than @@create, such that the exact implementation details might not straightforwardly translate to implementations. But then again, implementers generally dislike @@create, so I don't think my intuition here is very good.

# Kevin Smith (10 years ago)

Thanks Claude for working this up.

InitializeThisBindings(nonconstructedObj, obj) abstract operation

This operation performs the actual initialisation of the this-bindings that were previously deferred:

  1. Assert nonconstructedThisObj is a Non-Constructed Object.
  2. Assert obj is an ordinary object.
  3. Replace all references to nonconstructedThisObj with references to obj. (In particular, this step will effectively initialise the this-binding of every function environment record that used to reference nonconstructedObj.)

I was vaguely thinking along similar lines last week, but was stumped at this point. Is step #3 possible? You'd basically have to (magically?) replace the "this" binding for any subclass constructors on the call stack. This is probably a question for Allen.

# Claude Pache (10 years ago)

Le 29 juin 2014 à 00:36, Domenic Denicola <domenic at domenicdenicola.com> a écrit :

This looks nice to me as well. But given the length I had trouble internalizing in what externally-visible ways it would change the status quo, especially with the optional @@create addition. The writeup as-is is very spec-language-focused. Would it be simple to summarize that for us?

I plan to rewrite new version of the proposal very soon, with a less spec-language-oriented description (and with some amendments for better handling edge cases).

The problem of observability of created-but-not-initialised instances is not exactly solved in presence of the @@create hook. I think that the main interest is that without that hook, subclassing does work and the aforementioned problem is solved.

# Claude Pache (10 years ago)

Le 29 juin 2014 à 04:46, Kevin Smith <zenparsing at gmail.com> a écrit :

Thanks Claude for working this up.

InitializeThisBindings(nonconstructedObj, obj) abstract operation

This operation performs the actual initialisation of the this-bindings that were previously deferred:

  1. Assert nonconstructedThisObj is a Non-Constructed Object.
  2. Assert obj is an ordinary object.
  3. Replace all references to nonconstructedThisObj with references to obj. (In particular, this step will effectively initialise the this-binding of every function environment record that used to reference nonconstructedObj.)

I was vaguely thinking along similar lines last week, but was stumped at this point. Is step #3 possible? You'd basically have to (magically?) replace the "this" binding for any subclass constructors on the call stack. This is probably a question for Allen.

In fact, what I really need, is that the this-binding acts like a const-binding: It may be uninitialised at first, and will be initialised after the first call to a super-method (in most non-buggy code, it will be the super-constructor).

# Boris Zbarsky (10 years ago)

Le 27 juin 2014 à 15:56, Claude Pache<claude.pache at gmail.com> a écrit :

Here is a counter-proposal (or an improved proposal, ad libidum), which is not tightly coupled to @@new.

This proposal seems ok to me from my DOM-centric perspective. ;)

Optional: the @@create hook

A @@create hook can easily be placed as follows: In each [[Call]] internal methods defined above, a call to (thisArgument.[[Constructor]]).@@create could replace the steps spanning from GetPrototypeFromConstructor(...) inclusive to InitializeThisBindings(...) exclusive.

Whether such a hook is compatible with, e.g., DOM constructors, is left to the appreciation of the competent people. At worst, a built-in constructor could cheat by defining its own [[Call]] internal method that would refuse to run the @@create hook.

The problem with @@create is not so much whether built-in constructors call it. It's whether user code can invoke built-in @@create hooks and hence observe partially-constructed object.

However, with this proposal it seems easy enough to just not provide any such built-in @@create hooks, right?

# Claude Pache (10 years ago)

Here is an updated version of my proposal. Superficially, there are notable changes on how the things are presented, but the observable behaviour remains basically the same.

For a quick understanding, the Simple Description should suffice. In the Detailed Semantics, I’ve tried to give just enough details to make the precise semantics clear.

Simple Description

If a constructor C does not use super, it is assumed to not be a subclass, and it has the ES5- behaviour (or, equivalently, the current specced behaviour without the user-overridable-and-callable @@create hook), except that the [[Prototype]] may not always be taken from C.prototype, but from some D.prototype, where D is a subclass of C as described below.

If a constructor C uses super in its code, let’s say:

class C extends B {
    constructor(...args) {
        /* 1: preliminary code that doesn't contain calls to a super-method */
        // ...
        
        /* 2: call to a super-constructor */
        super(...whatever)
        
        /* 3: the rest of the code */
        // ...
    }
}

the following occurs when C is invoked as constructor, e.g. using new C(...args):

  1. During phase /* 1 */, the this-binding is uninitialised; trying to access it through an explicit this keyword will throw a ReferenceError.

  2. At phase /* 2 */, a call to the super-constructor (or, indeed, any super-method call) will in fact invoke it with the semantics of a constructor, and will initialise the this-binding. It is somewhat as if doing,

    this = new super(...whatever)
    

    except that the default prototype of the created object will be D.prototype rather than super.prototype, where D is the constructor on which the new operator was originally applied (the reference to D being forwarded by the super-call as needed).

  3. During phase /* 3 */, the this-binding is initialised, and any call to a super-method has the normal semantics of method (not constructor).

That’s it.

The fact that the super is called with the semantics of a constructor means that subclassing will just work, including for functions that have different behaviour when used as constructor and when used as function or method. This is the case, e.g., for bound functions (see 1), or, say, Date (that is, without the need of some hack).

Note that constructor(...args) { super(...args) } has now the same meaning as constructor(...args) { return super(...args) } (when called as constructor), so that the issue mentioned in bug 2491 needs to be reconsidered. This is resolved by tweaking Object.[[Construct]], as shown at the end of that message.

The @@create hook is gone, in order to avoid completely the observability of partially-constructed built-in objects.

Also, there is no @@new hook 2, because the constructor method is the @@new hook. A notable fact is that a constructor can always override its default constructed object by returning another object, which is a legacy feature of ES5- non-subclassable constructors.

(And, in order to give credit where it is due, I must mention that it is the @@new proposal of Jason 2 which has been the starting point of my reflection, by trying to give the most rational semantics of the @@new behaviour.)

Detailed semantics

Additional semantics for [[Call]] internal method

Recall that the call behaviour of a function is encoded in the [[Call]] internal method. Besides its current behaviour, the following features are added.

The signature has a supplementary optional argument thisConstructor:

F.[[Call]] (thisArgument, argumentsList, thisConstructor)

The intent of thisConstructor is to keep track of the original constructor on which a new operator was applied.

The thisArgument may receive the special value empty, meaning that the this-binding will not be initialised; it may be initialised once (as for a const-binding).

Moreover, the Completion record (either normal or abrupt) returned from [[Call]] will hold an additional [[thisValue]] field, which, unless otherwise specified:

  • is set to the original thisArgument if it was not empty; or,
  • is set to the value (at the time of completion) of the this-binding, if it was initially uninitialised but has been initialised; or,
  • is absent if the this-binding was left uninitialised.

(If an abrupt completion is forwarded, its [[thisValue]] field shall be modified as needed.)

We will use the following notations (they are properly methods of function environment records):

  • GetThisBinding() ― get the value of the this-binding of the appropriate function environment record.
  • InitializeThisBinding(value) ― initialise an uninitialised this-binding.
  • GetThisConstructor() — retrieve the thisConstructor value that was passed to [[Call]].

Modified semantics of the [[Construct]] internal method

The [[Construct]] internal method determines the behaviour of a function when invoked as constructor. Relatively to what is currently specced, it has a supplementary argument receiver:

F.[[Construct]] (receiver, argumentsList)

The argument receiver is intended to receive the reference to the original constructor on which a new operator was applied. In particular, new F(...args) will trigger F.[[Construct]](F, args).

Main differences from the currently specified [[Construct]] internal method are:

  • when effectively constructing the object, the prototype is searched on receiver.prototype rather than F.prototype;
  • the Completion record returned by [[Construct]], if not abrupt, shall have its [[value]] field and its [[thisValue]] field set to a same value of type Object. (That condition is here for easing the subsequent use of the Completion Record.)

Also, it will be handy to use the following notation, where R is a Completion record typically returned from [[Call]]:

ReturnNormalizedConstructCompletion(`R`).

as an abbreviation for:

  1. If R is an abrupt completion, return R.
  2. Else, if Type(R.[[value]]) is Object, return Completion{[[type]]: normal, [[value]]: R.[[value]], [[thisValue]]: R.[[value]]}.
  3. Else, if R.[[thisValue]] is present and Type(R.[[thisValue]]) is Object, return Completion{[[type]]: normal, [[value]]: R.[[thisValue]], [[thisValue]]: R.[[thisValue]]}.
  4. Else, throw a ReferenceError, with its [[thisValue]] field set to R.[[thisValue]] if R.[[thisValue]] is present.

F.[[Construct]] (receiver, argumentsList) for user-defined functions

When F is a user-defined, non-arrow function its [[Construct]] internal method does the following.

  • If F does not use super (that is, in spec language, if F.[[NeedsSuper]] is false):

    1. Let obj be the result of OrdinaryCreateFromConstructor(receiver.prototype, "%ObjectPrototype").
      // this is roughly obj = Object.create(receiver.prototype || Object.prototype)
    2. ReturnIfAbrupt(obj).
    3. Let result be the result of F.[[Call]](obj, argumentsList, receiver).
    4. ReturnNormalizedConstructCompletion(result).
  • If F uses super, the creation step is deferred:

    1. Let result be the result of F.[[Call]](empty, argumentsList, receiver).
    2. ReturnNormalizedConstructCompletion(result).

Semantics of a super-method call

When, say, super.method(..args) is called, the following steps are taken:

  1. Let F be the method referenced by super.method.
  2. Let thisValue = GetThisBinding().
  3. If thisValue is not empty,
    a. Let result be the result of F.[[Call]](thisValue, args).
  4. Else,
    a. Let receiver = GetThisConstructor().
    b. Let result be the result of F.[[Construct]](receiver, args).
    c. If Type(result.[[thisValue]]) is Object,
    i. InitializeThisBinding(result.[[value]]).
    d. Assert: If the test in previous step was negative, then result is an abrupt completion.
  5. Return result.

Special behaviour of the Object constructor

The [[Construct]] internal method of Object:

Object.[[Construct]] (receiver, argumentsList)

has the following semantics:

  1. If receiver is Object,
    a. Let result be Object.[[Call](undefined, argumentsList).
    // this is the usual factory function.
  2. Else,
    a. Let result be OrdinaryCreateFromConstructor(receiver, "%ObjectPrototype").
    // which is roughly Object.create(receiver.prototype || Object.prototype)
  3. ReturnNormalizedConstructCompletion(result).

The test of step 1 distinguishes between invocations of [[Construct]] coming directly from new from those triggered by super. That will gracefully handle accidental calls of the Object constructor that occur in situations described in bug 2491.

# André Bargull (10 years ago)

Moreover, the Completion record (either normal or abrupt) returned from [[Call]] will hold an additional [[thisValue]] field, which, unless otherwise specified:

  • is set to the original thisArgument if it was not empty; or,
  • is set to the value (at the time of completion) of the this-binding, if it was initially uninitialised but has been initialised; or,
  • is absent if the this-binding was left uninitialised.

Hmm, mixed feelings about extending the Completion record type to hold another field. It could be problematic for implementors to store this additional state efficiently. On second thought, it should be possible to merge [[thisValue]] with the existing [[Value]] field, and conditionally handle [[Value]] == empty in [[Construct]]? [1]

Moreover, the Completion record (either normal or abrupt) returned from [[Call]] will hold an additional [[thisValue]] field, which, unless otherwise specified:

  • is set to the original thisArgument if it was not empty; or,
  • is set to the value (at the time of completion) of the this-binding, if it was initially uninitialised but has been initialised; or,
  • is absent if the this-binding was left uninitialised.

Tail-call semantics may interfere with determining the value of "this-binding at the time of completion", because the execution context was already popped from the stack at that time. Probably solvable by determining the this-binding before performing the tail-call, plus some special casing when the tail-call expression is a super-call.


When, say, super.method(..args) is called, the following steps are taken:

  1. Let F be the method referenced by super.method.

This part needs to be redefined. super.method is an abbreviation for:

  1. Let env be GetThisEnvironment().
  2. Let baseValue be GetSuperBase(env).
  3. Let actualThis be GetThisBinding().
  4. Let result be baseValue.[[Get]]("method", actualThis).

But at this-binding initialisation time, actualThis is still the empty placeholder object, so it cannot be used as the receiver argument for the [[Get]] internal method call.

[1] gist.github.com/anba/e6b525c124d09dafaed6

# Kevin Smith (10 years ago)
  1. During phase /* 1 */, the this-binding is uninitialised; trying to access it through an explicit this keyword will throw a ReferenceError.

This seems overly restrictive to me. The common case (as in ES5) will be classes that derive from Object, where no such restriction is necessary.

The crux of the problem is that some host-defined classes that cannot safely separate object allocation from initialization. What if the @@create hook was allowed to return undefined? In such a case, your "uninitialized" semantics would apply: attempting to dereference "this" before calling super(...) would throw an error, and super(...) would essentially set the "this" value? In all other cases, the currently drafted semantics would apply.

# Claude Pache (10 years ago)

(Sorry for the delay of response, I am currently in a remote area.)

Le 7 juil. 2014 à 19:02, Kevin Smith <zenparsing at gmail.com> a écrit :

  1. During phase /* 1 */, the this-binding is uninitialised; trying to access it through an explicit this keyword will throw a ReferenceError.

This seems overly restrictive to me. The common case (as in ES5) will be classes that derive from Object, where no such restriction is necessary.

(To be more precise, the intended restriction is a TDZ, just like const declarations.)

There are trade-offs that should be made. Are there many cases where you want to manipulate this before calling super?

The crux of the problem is that some host-defined classes that cannot safely separate object allocation from initialization. What if the @@create hook was allowed to return undefined? In such a case, your "uninitialized" semantics would apply: attempting to dereference "this" before calling super(...) would throw an error, and super(...) would essentially set the "this" value? In all other cases, the currently drafted semantics would apply.

One feature of the proposal is that the first call to super invokes the constructor with the semantics of [[Construct]] rather than [[Call]], so that subclassing classes that have different construct/call behaviour would just work. It implies that there is no preliminary creation step.

# Claude Pache (10 years ago)

(Sorry for the delay of response, I am currently in a remote area.)

Le 6 juil. 2014 à 20:15, André Bargull <andre.bargull at udo.edu> a écrit :

Moreover, the Completion record (either normal or abrupt) returned from [[Call]] will hold an additional [[thisValue]] field, which, unless otherwise specified:

  • is set to the original thisArgument if it was not empty; or,
  • is set to the value (at the time of completion) of the this-binding, if it was initially uninitialised but has been initialised; or,
  • is absent if the this-binding was left uninitialised.

Hmm, mixed feelings about extending the Completion record type to hold another field. It could be problematic for implementors to store this additional state efficiently. On second thought, it should be possible to merge [[thisValue]] with the existing [[Value]] field, and conditionally handle [[Value]] == empty in [[Construct]]? [1]

An issue here is that I want to initialise the this-binding of the caller even when the callee ends abruptly (with throw).

Moreover, the Completion record (either normal or abrupt) returned from [[Call]] will hold an additional [[thisValue]] field, which, unless otherwise specified:

  • is set to the original thisArgument if it was not empty; or,
  • is set to the value (at the time of completion) of the this-binding, if it was initially uninitialised but has been initialised; or,
  • is absent if the this-binding was left uninitialised.

Tail-call semantics may interfere with determining the value of "this-binding at the time of completion", because the execution context was already popped from the stack at that time. Probably solvable by determining the this-binding before performing the tail-call, plus some special casing when the tail-call expression is a super-call.


When, say, super.method(..args) is called, the following steps are taken:

  1. Let F be the method referenced by super.method.

This part needs to be redefined. super.method is an abbreviation for:

  1. Let env be GetThisEnvironment().
  2. Let baseValue be GetSuperBase(env).
  3. Let actualThis be GetThisBinding().
  4. Let result be baseValue.[[Get]]("method", actualThis).

But at this-binding initialisation time, actualThis is still the empty placeholder object, so it cannot be used as the receiver argument for the [[Get]] internal method call.

[1] gist.github.com/anba/e6b525c124d09dafaed6

Here is another setting that I've thought of: When super is called with a not-yet-initialised this-binding, the this-binding of the callee is in fact a special pointer to the function environment record of the caller, and when the callee wants to read or update it's this-binding, it reads or update the one of the caller.