The global object should not be the "global scope instance object"

Allen Wirfs-Brock allen at wirfs-brock.com
Wed Jan 25 16:11:06 PST 2012


The following proposal questions the validity of the teeshirt maxim "Let is the new var" as a rationale for treating global |let| declarations similarly to ES<=5 global |var| declarations. While we want people to use |let| (and |const|) in preference to |var| we don't want to unnecessarily perpetuate undesirable aspects of the current browser ES global environment  that aren't strictly needed to maintain compatibility with existing code.

One of those undesirable aspects is the use of properties of a host provided object to represent the bindings of ES global declarations.  This results in undesirable consequences for both ES and the host environment such as HTML id attributes over-writing declared or built-in ES bindings or program declarations inadvertently causing host environment side-effects by unintentionally writing to host properties. We should not be trying to propagate the undesirable host pollution characteristic of global |var| to global |let| and |const| simply so we can encourage ES6 developers to replace existing uses of |var| with |let|.  Instead, this proposal suggests limiting the host pollution semantics to its existing use in |var| (and unfortunately |function|) and keeping a clean semantics for |let| and |const|. Going forward, ES programmer who need to extend the host's global object should be encourage to do so via explicit object operations (such as assignment to |this| properties or the window binding in the browser. They can continue to also do so using |var| but not via |let|,  |const|, or global |import|.

Here is a sketch of the proposal:

In ES6, the default (module loaders can change this) "top level" consists of two environment records.  The outmost environment is a an object environment record whose binding object is the "global object" (the window object in browsers). We will call this environment record the "host environment".   The second environment  is a declarative environment record whose outer environment is the host environment. We will call this the "top-level  environment".

When evaluating a Program all |var| and |function| declarations at the top level of the program create (if not already present) bindings in the host environment. In other words, top level vars and functions create properties on the global object.  So do  |this| qualified property assignments at the top level.  The declaration instantiation rules for such |var| and |function| declarations are the same as for ES5.1 (with the correction from https://bugs.ecmascript.org/show_bug.cgi?id=78 ) with the addition of rules about conflicting let/const/var declarations. There is no temporal dead zone for bindings created in the host environment because all such bindings are immediately initialized when they are instantiated.

For example, these all create or change the value of a property on the global object (unless prevented by property attributes or the extensibility of the global object):

<script>
var foo;
print('foo' in this);  //should be true
</script>

<script>
function bar() {};
</script>

<script>
var bar = "bar";
function bar() {};
</script>

<script>
this.baz="baz";
</script>

We will, for the moment, defer  discussing whether or not

<script>
bam="bam";
</script>

creates a property on the global object.

The following would each produce an early error

<script>
var foo;
let foo;
</script>

<script>
var foo;
const foo="foo";
</script>

<script>
var foo() {};
let foo;
</script>

<script>
function foo() {};
const foo="foo";
</script>

When evaluating a Program all |let| and |const| declarations at the top level of the program attempt to create  bindings in the top-level environment. Bindings imported  at the top level from modules are treated similarly to |let| or |const| bindings (as appropriate) except that multiple equivalent imports are allowed . Other than duplicate imports, any attempt to instantiate a binding in the top-level environment will fail if it already has a binding for the same name. This produces what is essentially an early error (it occurs before any user code for the program is evaluated).  If the identifier bound by a |let|, |const|, or |import| is also bound in the host environment (ie, it is the name of a property of the global object) the binding in the top-level environment shadows the host binding.  All temporal dead-zone rules apply to binding in the top-level environment;

Some examples,

<script>
let foo=1;
print('foo' in this);  //should be false
</script>

<script>
bar = "host";  //assume no "top-level" declarations for bar have yet been processed
print('bar' in this);  //should be true
</script>
<script>
const bar = "top-level"
print('bar' in this);  //should still be true
print(bar);    //"top-level"
print(this.bar); ;/"host"
</script>

Now back to 

<script>
bam="bam";
</script>

Whether this creates a binding in the global object depends upon what other script blocks have preceded it.  If a preceding Program had a let/const/import declaration for "bam" then this reference will resolve to the top-level environment.  If it is not preceded by such a Program then it will resolve to the host environment and either create or update a property of the global object.

Additional discussion/issues/concerns:

I believe that this approach is different from other proposal for using declarative environment records for Program scopes in that it does not use a distinct declarative environment record for each Program.  Instead it uses a single declarative environment record that is dynamically extended when evaluating each Program.  This is similar to what ES5 requires for function declarative environments when processing non-strict direct evals. This requires appropriate semantic rules for dynamic additions of |let|/|const| binding to a declarative environment record.  However, this will also be required to support ES6 non-strict direct eval and indirect (or program top level) non-strict evals of Programs that contain |let| or |const| declarations.

There are script block ordering dependency that can affect the meaning of a web app.  So far, that seems to be a characteristic of all the proposals we have considered.

It would be really nice to be able to bind |function| declarations in the top-level environment.  However, there are undoubtably existing web pages that depend upon such declarations creating properties of the global object. 

In the above I haven't addressed which environment is used to bind the ES built-in globals.  I'm assuming, that for compatibility reasons, these need to continue to be bound in the host environment (ie, properties of the global object).  I would prefer to place them in a separate enviornment record.  If that was plausible (which I doubt) I would have to give further thought to whether the environment for built-ins would be between the host environment and the top-level environment (ie shadowing the host environment and shadowed by the top-level) or shadowed by the host environment.




-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120125/80db5b4b/attachment.html>


More information about the es-discuss mailing list