Re: indexed properties on NodeLists and HTMLCollections

Cameron, good questions.  I can see that I need to be particularly careful in my language because this topic  spans different levels of the reflective tower and requirements and best practices may differ depending upon which meta-level you are addressing.  there are also differences between what may be need to support legacy API and practices that we should adopt for new APIs.

Proxies provide a reflective  behavioral intercession  facility for JavaScript.  I recommend reading http://soft.vub.ac.be/Publications/2007/vub-prog-tr-07-16.pdf for a detailed discussion of what this means but the short story is that Proxies provide a reflective way to modified the core object semantics of the language in specific contexts.  This is a powerful capability that enables some key scenarios like implementing classic NodeList semantics in pure JavaScript.  However,  such changes to the core object semantics should be very limited.  Otherwise, you get an anarchistic environment where it is impossible to reliably reason about the behaviors of objects.

Ideally (from a JavaScript perspective), we should be designing web APIs which only depend upon the standard native JavaScript object semantics and which don't require behavioral intercession.  In other words, APIs that can be directly implemented in JavaScript without resorting to the use of Proxy. This is particularly true for new APIs.  From a Web API perspective we should look at  Proxy primarily as a features that allows JavaScript to implemented legacy APIs that were not designed to respect native JS object semantics.


On May 4, 2011, at 5:32 PM, Cameron McCormack wrote:

> Allen Wirfs-Brock:
>> 
>> ...
>> WebIDL should use explicit [[Get]] and [[Put]] definition to specify
>> these semantics.
> 
> Hmm.  Not requiring specific [[Get]] and [[Put]] behaviour because they
> are specification devices and not hooks for other specifications to use
> as extension points is what I heard a while ago, and why I’ve been
> trying to move away from using them.  But I suppose that this was
> motivated by the fact that, without proxies, it wasn’t possible to
> implement their non-native-object behaviour with native JS.

Even though you are defining an ECMAScript binding for objects defined using WebIDL, the implementation of those objects will not necessarily be in ECMAScript.  The ES binding needs to define the ECMAScript observable behavior of NodeList and friends in a manner that will be consistent regardless of whether they are actually implemented in C++ or in Proxy enhanced JavaScript or in some other language.

Proxies are just an JavaScript implementation mechanism that supports changing the  [[Get]]/[Put]] (an others) semantics.  Somebody still needs to specify what [[Get]]/[[Put]] semantics is required and that specification should not assume use of Proxy as the actual implementation might be in C++ or other another language using a host object extension mechanism.

In terms of prioritization of specification techniques within the ES/WebIDL binding I suggest:
1) normal ES data property and method invocation semantics
2)  use of ES accessor properties in  possibly creative ways
-------------------  stop here for all new APIs
3) redefine the [[ ]] semantic hooks of ES that will trap through to proxies

For the last alternative you probably don't want to use Proxies as the basis of the specification but you want to make sure that the specification can be implemented via Proxies.

> 
> Presumably proxies will be specified part of the JS language in the
> future, exposed like the rest of the built-in objects.  Why would they
> be more of an implementation device then than custom [[Get]]/[[Put]]
> definitions?  Wouldn’t it be safer for us to define behaviour in terms
> of specific Proxy objects so that we can be sure that what we define is
> implementable in pure JS?

see all of above
> 
>> In doing so, you should try to select a semantics that makes sense
>> for a usage perspective. It also doesn't have to match the normal
>> [[Get]]/[[Put]] semantics nor does it have to conform to the normal
>> [[DefineOwnProperty]] semantics (eg, it might disallow allow the
>> creation of non-configurable array indexed properties).
> 
> OK.  This also seems counter to what I have been hearing – that we
> should attempt to align with native JS object semantics unless
> absolutely necessary.  I think in this case, the less native semantics
> is easier for authors to understand (and has the desired performance
> characteristics) so we should go with that if it is acceptable.
> 
> Disallowing the creation of non-configurable properties on NodeLists
> that look like array index properties would help us avoid the problem
> with Proxies not being able to expose a property as non-configurable at
> one point and configurable the next.

In the quote, I was really thinking in the context of how you go above describing legacy interfaces that don't conform to native JS object semantics.  The desire to support NodeList and friends in JavaScript based DOM-implementations was one of the primary motivations for developing the Proxy mechanism for JS.  We knew that such objects didn't conform to native object semantics and needed a mechanism that allowed an implementation to selectively over-ride the standard semantics.

I think the questions being raised here are probably 2nd degrees issues that arise because there are currently implementation differences in the non-native semantics of these objects.  In reconciling these differences we need to decide on some design criteria we want to apply. Given that we are already dealing with non-native JS object semantics, I suggest the following criteria:
1) try to define a semantics that is internally (to the abstraction such as NodeList) consistent and that supports the common legacy semantics of the abstraction while still conforming to the host object restrictions specified in ES5 section 8.6.2
2) follow native JS semantics and conventions except where it would be in conflict with #1

>> ...
> 
> OK.
> 
> About the requirement for Proxies to expose only configurable
> properties: would that mean that a NodeList object, implemented as a
> Proxy, can’t preserve the non-configurability of any properties on it?

Where is this requirement?  Is it something in the Proxy proposal?  or are you inferring something from the ES5 section 8.6.2 host object restrictions?

> For example:
> 
>  var list = document.documentElement.childNodes;
>  // list is a NodeList that happens to be implemented by a Proxy
>  Object.defineOwnProperty(list, "a", { configurable: false, value: 1 });
>  alert(Object.getOwnPropertyDescriptor(list, "a").configurable);
> 
> would that alert true?  If so, is that seen as a problem?

I would expect it to alert true.  "a" isn't a property name that is "live" as the one of the items of a NodeList.  Hence, it should just have normal [[Put]]/[[Get]]//DefineOwnProperty]] behavior.  It would be up to the proxy based implementation to keep track of which properties are special (in this case it is the array index property names) with attribute restrictions and which follow normal native object semantics. If there is something in the Proxy specification that makes this impossible then I think it is a problem with Proxies that needs to be fixed.

The situation would be different if list was a NamedNodeMap.  In that case "a" (or any valid nodeName value) could be the name of a "live" property that could be dynamically added to the list. You need to specify the desired semantics for such a live property when its property name already exists and is either non-configurable or non-writable (and also if the object is non-extensible).  Either it needs to be specified to not get added in these situations or it needs to be specified that the properties of a NamedNodeMap may not be made non-configurable (or the object non-extensible).   For the later semantics I would expect the defineOwnProperty call would throw when setting configurable to false on any NamedNodeMap property.  Either semantics presumably could be handled by a Proxy-based implementation.


The host object restrictions on [[GetOwnProperty]] in 8.6.2 are these to ensure that a JS programmer always has access to reliable information about the mutability (and hence cache-ability) of objects (including host objects) and their properties.  If you can't guarantee that a property value that is "readonly" will never change or you can't prevent a property from being added to a non-extensible object then you must disallow attempts to set the object/property attributes to those states.  You could silently ignore the attempt but it is probably preferable to thrown an exception. 


Allen

Received on Thursday, 5 May 2011 19:00:38 UTC