The global object as the "global scope instance object"

Andreas Rossberg rossberg at google.com
Sat Jan 21 00:25:49 PST 2012


On 21 January 2012 01:23, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
> Temporal dead-zone initialization tracking requires extra state and checking
> (except for the common cases within functions where it can be statically
> determined that there are no possible references within the dead zone).
>  This additional requirement is reflected in the specification in the ES5
> definition of the CreateImmutableBinding and InitializeImmutableBinding
> environment record methods.  In ES5 this mechanism (and state) was only
> needed within DeclarativeEnviornmentRecords for (the rarely used) immutable
> bindings. For the ES6 spec. this has been generalize to also apply to
> ObjectEnvironmentRecords and to track initialization of both mutable and
> immutable bindings.

In my proposal, I think you wouldn't need this generalisation.
Uninitialised state needs to be tracked for environment bindings only,
not for objects. The indirection through accessors makes the latter
unnecessary.

>> Hence, I would like to suggest another, more moderate idea for a
>> global scope reform: treat the global object similar to a module
>> instance objects. That is:
>>
>> - Top-level bindings (other than var) are reflected as accessor
>> properties on the global object.
>> - They are defined on the global object before the script starts executing.
>> - Their getters/setters simply delegate to the bound variables.
>
> whose access presumably perform the necessary checks to enforce the temporal
> deadzone.

Yes.

>> - For const bindings, there are no setters.
>> - These properties are non-configurable.
>
> It is already the case (for ES5) that all properties created for global
> declarations are non-configurable (unless created by an eval).
>
>> - Unlike a module instance object, the global object is extensible
>> (multiple scripts!).
>>
>> This disallows lexical bindings to redefine existing non-configurable
>> properties of the global object (including ones reflecting bindings
>> from earlier scripts), which is checked before the script starts
>> executing. Likewise, let/const-defined properties cannot later be
>> deleted from the global object.
>
> I'm not yet convinced that you need to publicly expose such global object
> properties accessor properties.  I think it is possible to hide the
> mechanisms.

I don't think so, at least not as cleanly.  With accessors, you avoid
the whole notion of uninitialised object property, which is painful
and pretty intrusive.

In any case, we should do the same for the toplevel and for modules.


> Independent of implementation schemes, I think we need to decide on the
> meaning of the following independent scripts (in different documents)

Those are the kinds of examples I was alluding to. And the main point
of my proposal, and the use of accessors, is that those examples get a
natural and coherent meaning, without requiring any special rules.
Namely:

> <script>
> "do not use strict";
> //window.foo does not exist at this point.
> foo = 10
> const foo=5;  //or let
> print(foo);
> </script>

The effect of this will be the same as in any other scope, e.g.:

{
  foo = 10
  const foo=5;  //or let
  print(foo);
}

I.e., the first assignment is an (early) error, because you can't
assign to a const. If it was a let, it would still be a temporal dead
zone violation. The global object doesn't enter the picture at all.

> <script>
> //window.foo does not exist at this point.
> window.foo = 10
> const foo=5;  //or let
> print(foo);
> </script>

This behaves the same as:

module W {
  W.foo = 10
  const foo=5;
  print(foo);
}

which in turn behaves like:

Object.defineProperty(window, "foo", {get: function() { return foo }})
window.foo = 10
const foo = 5

I.e., the assignment again is an error, this time because there is no
setter for foo. If it was a let, then you'd have a temporal dead zone
violation in the setter.

> <script>
> //window.foo does not exist at this point.
> print(foo);
> const foo=5;  //or let
> print(foo);
> </script>

Again, same as in any other scope: temporal dead zone violation on the
first print.

The idea is that the top level is just another declarative scope, and
all rules regarding its reflection in the global object mirror the
rules for module instance objects. Less rules, less pain. :)


>> Regarding scope pollution, I think this is best solved by making the
>> global object be a fresh empty object that has the object containing
>> all primordials as its prototype. Overriding is allowed according to
>> the standard defineProperty semantics. No special rules necessary.
>
> In that case, why is it an object at all?  Why not simply a
> DeclarativeEnvironmentRecord in an environment whose outer is a
> GlobalEnvironmentRecord whose backing object is the global object?

That's more or less what I am thinking, if, by "global object" in that
sentence, you mean the object carrying the (other) primordials (as
opposed to the "toplevel instance object").

The reflection of the toplevel scope as its own object becomes
secondary, and does not affect the environment semantics. The object
is only used as a value for toplevel `this'.

The only complication for this scheme might be toplevel var, which
currently does not shadow (all) external global bindings. That is
perhaps worth changing for extended mode.

> I'm actually think along some similar lines but I think this direction raises
> interesting questions, going forward,  about about some current fundamental
> assumptions:
>
> What is the role of the global object?

That's what I'm trying to answer with my proposal: the new global
object is merely the "top-level instance object", in analogy to module
instance objects. Only needed as a value for `this'.

> Must all ES global declarations reify as properties on the global object?
> If not, which global declarations must reify as such?  (what are the actual
> web dependencies).
> Must all built-in globally accessible bindings reify as global object
> properties?

I only see two sensible answers: all or none. I was assuming the former.

Of course, you could go even further down the road of module-like
behaviour, and let the programmer decide using export declarations.
But that seems like overkill, and isn't backwards compatible either,
so I wouldn't propose it.

/Andreas


More information about the es-discuss mailing list