The global object as the "global scope instance object"

Andreas Rossberg rossberg at google.com
Mon Jan 23 05:54:38 PST 2012


On 22 January 2012 23:46, Gavin Barraclough <barraclough at apple.com> wrote:
> On Jan 20, 2012, at 4:23 PM, Allen Wirfs-Brock wrote:
>> 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'm strongly inclined to agree with this sentiment – engine implementors
> might choose internally to utilize accessors in their implementations, but
> from a user perspective it seems to make much more sense that these remain
> as regular data properties.  It is worth bearing in mind that 'const' is
> already available outside of strict mode and we need to be considerate of
> any breaking changes we make to existing semantics.

Sigh. This may be exactly the reason why I am deeply sceptical that
backporting ES6 features to classic mode is a wise move. Now we don't
just have to stay backwards compatible with broken ES5 features, we
even have to stay backwards compatible with broken ES5 non-features?
Even in strict mode?

Harmony was supposed to be based on strict mode for a reason.


> (We could define
> incompatible semantics for strict-mode only, but this would likely lead to a
> confusing divergence in semantics for users – it could be horribly confusing
> if const variables in non-strict code were to be implemented as data
> properties whilst those in strict code were defined as accessor properties).
>
> alert('value' in Object.getOwnPropertyDescriptor(this, 'x'));
> alert(Object.getOwnPropertyDescriptor(this, 'x').writable === true);
>
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> x = 1;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> const x = 2;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> x = 3;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>
> This script tests the presence and writability of a property on the global
> object, and the effect of attempting to assign to it in non-strict code.  It
> demonstrates a non-const var behaving as a regular non-writable property in
> non-strict code.
>
> On FireFox the above script outputs "true, false, undefined, undefined, 2,
> 2", which to my mind makes a lot of sense.  It perfectly matches, as far as
> one can test, any other regular non writable property that you could create
> on an object (of course there it not normally the opportunity to split
> creation and initialization of a non-writable property).

I disagree completely. This is the current V8 semantics as well, but
it does _not_ make sense.

You observe that the value of a const is changing in the middle of
your program. That fundamentally violates const semantics, and is
incompatible with the idea of guards on let as well.

Furthermore, the property descriptor tells you that the property is a
writable data property, but you cannot write it (e.g. replacing "x =
1" with "this.x = 1"). That violates the object model.

In other words, the current behaviour breaks both scoping and objects semantics.


> This behaviour is also sensibly consistent with that for let and var.
>  Running the above script, changing 'const' to 'var'
> outputs "true, true, undefined, 1, 2, 3" (indicating the property is
> writable, and all assignments succeed), and for 'let' currently also
> outputs "true, true, undefined, 1, 2, 3" on FireFox.  To my mind this
> behaviour should probably change slightly.  With a temporal dead zone
> implemented I would expect an attempt to [[DefineOwnProperty]] to an
> uninitialized value to be rejected (though I would not expect an exception
> to be thrown from non-strict code) so I would expect the output for 'let' to
> be "true, true, undefined, undefined, 2, 3" (the assignment of 'a' silently
> fails leaving the value unchanged).

More importantly, an attempt to [[Get]] an uninitialized property also
has to be rejected. Both requires proliferating the entire notion of
being uninitialized into the object model. To be coherent, you'd also
have to be able to reflect on it through property descriptors, make it
available to proxies, and so on. I see no good reason why we should go
there.

> I would suggest that specifying this as a piece of additional hidden
> internal state on a data descriptor (rejecting attempts to define or access
> the value prior to initialization, throwing in strict-mode & silently
> ignoring in non-strict)

Hidden internal state? That seems to undermine the very idea of having
property descriptors to reflect the state of a property.

> makes most sense on a number of grounds:
>
>  – compatibility with existing non-strict const implementations.

Yeah, I think there is no reasonable way we can achieve that. Existing
const implementations aren't even compatible with each other. Const
was ruled out in strict mode for a reason.

>  – consistency with appearance of var properties on the global object.

Var is not lexically scoped, and has a very different semantics from
let. Breaking "consistency" with var is the whole point of lexical
scoping.

>  – consistency with appearance of regular non-writable properties on other
> objects.
>  – consistency with behaviour of access to regular non-writable properties
> on other objects.

It's not consistent, otherwise you couldn't observe writable === true,
and wouldn't need "hidden internal state".

>  – data properties typically have higher performance in implementations,
> specifying all global let & const properties to be accessors may introduce
> unnecessary overhead.

Actually, as a data point, performance of the toplevel is currently
poor in V8, _because_ every variable is reflected simply as a data
property directly, and hence has to be read and written through
immediately. The most effective way to optimize that is implementing
the global object using internal accessors (that look like data
properties). So in terms of _efficient_ implementation of the
toplevel, both approaches are equivalent, and as an implementer, I
don't care much either way.

Also, note that for ES6 modules, we will have to optimize these kinds
of accessors anyway.

One thing I noticed with your comments is that they consider the
global scope almost exclusively from the object perspective, i.e., as
special syntax for putting properties on the global object. But first
and foremost, it is a scope, so coherent scoping semantics is at least
as important. If it is incoherent with other scopes, that is
confusing, and a serious refactoring hazards. With static scoping,

/Andreas


More information about the es-discuss mailing list