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

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Jan 27 11:41:42 PST 2012


On Jan 26, 2012, at 4:57 PM, Brendan Eich wrote:

> Allen Wirfs-Brock wrote:
>> On Jan 26, 2012, at 11:51 AM, Brendan Eich wrote:
>>> We could say event handler attributes are scoped by the most-nested scope -- the scope due to the last <script> element that was closed before the element with the event handler attribute was processed.
>>> This means generated scripts (document.write or DOM create* calls) do not see the generating script's let and const bindings.
>> 
>> Why closed? All declarations within a /Program/ are instantiated before evaluating any code in its <script> element so generated scripts should be able to reference the bindings for those declarations.  However, if such reference are actually evaluated they might fall into the bindings' temporal dead zones.
> 
> You're right, the entire outer <script> has been parsed and declarations processed before anything runs. And the script element is appended to the DOM too. It all works, except of course you can't tell what's potentially shadowing anything!

Yes, this makes references to let and const bindings inherently unpredictable.  But also var and function bindings as they can be shadowed by let/const.  STL would at least give you an error that hopefully will show up in an error console.
  
> ...

My point was that we shouldn't try too hard to fix problems that aren't under our control. We need to be aware of them and should try to make things any worse.  But trying to fix async load issues in our spec. seems  like address the problem at the wrong level.

> 
>> <script src="someLib.js"  type="application/javascript" async="async" id="lib1"></script>
>> <script src="anotherLib.js"  type="application/javascript" async="async"id="lib2"></script>
>> <script src="baseApp.js"  type="application/javascript" async="async" id="base" after="lib1"></script>
>> <script src="extraApp"  type="application/javascript" async="async" after="lib2,base"></script>
> 
> Because that's brittle and slow -- you don't care about order, you typically just want to race and exploit as much parallelism as possible, given host CPU(s), the intervening network, TCP connection sharing/limits/etc.

But the ordering dependencies are real. If you don't explicitly identify them and they also aren't implicitly recognized then your application will be unreliable. 
> 
>>> The situation is messy enough that I question the win of nesting scopes. True, a global object subject to async-loaded effects is no picnic, but it is the devil we know. And these var and function bindings, however racy, are not supposed to be lexical, as let and const are.
>> 
>> I think these are the alternatives we currently have under consideration:
>> 
>> SQ (status quo):  every top level declaration are all implemented as properties of the global object
>> STL (Single Top Level):  All script blocks share a declarative environment for new (not var and function) kinds of declarations that shadows the global object. Scripts with duplicate declarations are rejected.
>> TLpSi (Top Level per script, isolated):  Each script block has a declarative environment for new (not var and function) kinds of declarations.  Each shadows the global object but are invisible to each other.
>> TLpSn (Top Level per script, nested):  Each script block has a declarative environment for new (not var and function) kinds of declarations.  Each is logically nest within and shadows the previously processed script
>> 
>> STL is what I'm proposing.
> 
> I'm pretty sure the collisions-are-rejected thing is going to burn. It's the opposite of what people not only abuse (unknown latent bugs), but absolutely use and rely on today, with var and function among scripts.

With STL we would be saying that if you need redefinition/multiple definition behavior you need to continue to use var and function or you need to refractor to use modules/export/input.  But you can'tuse naked let/const. Some people would get burned by this and conclude that var is always preferable to let.  But others would learn about modules.  In either case, we would have a consistent semantics for let/const.  BTW, we should make sure that modules can actually support the use cases that need such multi/redefinitions.  I think it is mostly various ways of doing polyfill like things.

> 
>> I think that TLpSn corresponds to  Andreas preference.  One way to look at TLpSn is that uses shadowing to accept the duplicate declarations that STL rejects.  This seems like a problem that is better addressed by using modules. TLpBn
> 
> (s/B/S/ right?)
yes
> 
>> also raises issues like, what scope does an indirect eval use?  I haven't head anyone recently advocating for TLpSi.  You can achieve almost the same thing using SQ by wrapping a block around each script body.
> 
> Right, although TLpSi is still attractive as a parallel to function bodies, where we agreed body-level let has to bind in a body block that shadows parameters and vars. Not much of a plus but it's a plausible alternative.

did you just say that this is legal:

function f() {
    var b;
    let b;
}

and interpreted as:

function f() {
    var b;
    { let b; }
}

That wasn't by understanding of the take aways from the Nov. meeting.

I kind of like TLPSi, but I think it would be counterintuitive for web developers who write in-line scripts such as:

<script>
const debug = true;
</script>
<-- a bunch of html -->
<script>
if (debug) {...}
</script>

If we only had src based scripts it might be the right thing...

> 
>> In general, when I start think about the ways that lexical declarations in separate scripts might interact I run into issue that are probably best resolved using modules. I'm inclined to favor the simpler solution and leave it to modules to deal with managing actual interdependencies cases.
> 
> Is STL the simplest solution for users?

It avoids issues like:

<script>
const debug = true;
function f1 () {
    if (debug) log(...);
    ...
}
</script>
<script>
const debug = false;
function f2 () {
    if (debug) log(...);
    f1();     // but this logs regardless
    ...
}
</script>
<script>
let debug=false;  //this has no effect
f1();
f2(); 
</script>

the behavior of this under TLpSn is perfectly normal given lexical scoping.  However, there is no physical nesting so it is likely to be surprising to many users.

Allen


> 
> /be
> 



More information about the es-discuss mailing list