Do we really need the [[HasOwnProperty]] internal method and hasOwn trap

Tom Van Cutsem tomvc.be at gmail.com
Tue Nov 13 12:25:56 PST 2012


2012/11/13 Allen Wirfs-Brock <allen at wirfs-brock.com>

>
> I think there is agreement that [[HasOwnProperty]] is just an optimization
> of ToBoolean([[GetOwnPropertuy]]).  Its only purpose is to avoid
> unnecessary reification of property descriptors. If that optimization isn't
> important we should just eliminate [[HasOwnProperty]].
>

I understand your point that on normal objects, implementations are free to
avoid actually allocating a property descriptor for has or hasOwn checks.
As far as the spec is concerned, the derived traps are completely
unnecessary: we could hypothetically rewrite the entire spec to use only
fundamental traps, and implementors would be free to cut as many corners as
they want for non-proxy objects.

When I mentioned that derived traps avoid unnecessary allocations, I was
really thinking about this from the point-of-view of the ES6
metaprogrammer. Say I'm creating my own virtual object abstraction whose
properties reside in an external map, so I dutifully implement all the
traps:

var store = new Map()
var p = new Proxy( { } /* target doesn't matter */ , {
  hasOwn: function(ignoreTarget, name) { return store.has(name); },
  getOwnPropertyDescriptor: function(ignoreTarget, name) {
    var e = store.get(name);
    return e === undefined ? undefined : {value:e, ...};
  },
  ...
};

Say we remove the "hasOwn()" trap, calling the getOwnPropertyDescriptor
trap instead. Now my virtual object abstraction does need to allocate a
descriptor. Your proposal of adding a flag to the trap fixes that, but IMHO
in a way that confuses the intent of the operation being intercepted:

var store = new Map()
var p = new Proxy( { } /* target doesn't matter */ , {
  getOwnPropertyDescriptor: function(ignoreTarget, name, needDescriptor) {
    if (!needDescriptor) { return store.has(name); }
    var e = store.get(name);
    return e === undefined ? undefined : {value:e, ...};
  },
  ...
};

It's dawning on me that the main reason I dislike adding flags to the
fundamental traps is that it breaks the 1-to-1 symmetry with the operations
that they intercept:

Object.getOwnPropertyDescriptor(proxy, name) // traps as
getOwnPropertyDescriptor(target, name)

This symmetry should make it relatively easy for an ES5 programmer to pick
up the handler API. The signatures look familiar (at least for all
operations that are expressed as function calls). Adding extra flags breaks
the symmetry.

We've explored the security issues or proxies in depth.  Now is the time to
> explore the usability issues.  Proxies are not intended for novices but
> there still will be thousands of developers trying to use proxies who are
> neither metaprogramming experts or  have a deep understand of the ES MOP.
>  We should try to minimize the number of footguns and other hazards that
> they have to face.
>

Allen, I'm completely on the same page here. I myself have previously
expressed concerns about the inconsistencies introduced by derived traps. I
think our disagreement lies only with how to deal with the inconsistencies.

So if I don't like adding extra flags, what's my counterproposal?

I knew I previously wrote about some of these issues, and I finally found
out where: an old strawman for the old Proxy API: <
http://wiki.ecmascript.org/doku.php?id=strawman:derived_traps_forwarding_handler
>.

That strawman defines two possible semantics for derived traps:
- “forwarding” semantics: forward the derived operation to the target
- “fallback” semantics: implement the “default” semantics in terms of the
fundamental forwarding traps

"Forwarding" semantics is what you get 'out of the box' with direct proxies.
To obtain "fallback" semantics for derived traps, you need to subclass
Handler.

I thought this fixed the footgun, except it doesn't deal with the case
where one overrides only the derived trap, but leaves the fundamental trap
in "forwarding" mode.

I just realized that in a previous version of the Handler API, the
fundamental traps were specced to be "abstract" methods (i.e. they would
throw when called). Given that semantics, the Handler API would avoid the
footgun entirely. For example:

1) if you subclass Handler and override the derived "hasOwn" trap, but
forget to implement the fundamental "getOwnPropertyDescriptor" trap, you'll
get a noisy exception (instead of inconsistent behavior).
2) if you subclass Handler and override the fundamental
"getOwnPropertyDescriptor", but forget to override "hasOwn", things will
just work (the subclass automatically inherits a compatible "hasOwn" trap).

So, my proposal: let's revert the fundamental traps of Handler to become
abstract methods again. This forces subclasses of Handler to provide all
fundamentals at once, avoiding the footgun.

I'm fully aware that this fixes the footgun *only* if the programmer
decided to subclass Handler. I think we need to do a better job
evangelizing the Handler API and calling out the benefits of using it (I
now know the topic of my next blog post on proxies ;-)

Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121113/ab5331c9/attachment.html>


More information about the es-discuss mailing list