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.




_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to