Objects, WeakMaps, and Proxies into a Membrane: The es7-membrane design
This wasn't terribly useful, but it was the only option available for many years. Later came WeakMap objects, which added objects as valid keys, provided you used the .get() and .set() methods to pass in both key and value:
You could similarly have a WeakMap that stored references the other way:
This requires, then, two WeakMaps, and defines a proper one-to-one relationship between objects in the "wet" object graph and objects (or proxies) in the "dry" object graph. But this is not the approach I took.
Instead, I created a hybrid of both systems. First, I defined an object
(let's call it
submap for now) which stores references to a
"wet" object, and also to a corresponding "dry" object (or proxy). Then, I
created another wrapper object around it:
Then I defined one WeakMap, and in that map, I required both
the "wet" and the "dry" values point to the
There are a number of advantages to this approach, in my view:
- I can easily define a "damp" object graph value on
submap, by simply adding a new property to
submap.proxiedFields. Similarly, there is no limit to the number of object graphs I can define.
- I can store flags and special proxy functions on the relevant member
submap.proxiedFieldsobject, such as a filter function for the "own keys" of a proxy.
- I can store other object graph metadata on the submap which is not specific to a given object graph, such as a reference to the next object in the prototype chain.
- I can generalize
ProxyMappingconstructor, and define methods on the
ProxyMapping.prototypeobject, for managing access to the various object graph values, fields, and special properties.
submap and the
though, does not a membrane make. It only defines a wrapper for how a
native value in one object graph relates to the equivalent proxies in
other object graphs. For that, we have to put the submaps and
ProxyMapping objects aside.
Membranes and Object Graphs
Borrowing from Tom van Cutsem's visualization of wet and dry object graphs, let's draw a similar diagram with triangles.
D3 are the "original"
objects (or functions).
d(W1) is a "dry" proxy to the "wet"
W1, and similarly,
d(W2) is a "dry" proxy
to the "wet" object
W2. The "wet" object graph also has a
proxy to the "dry" object graph, in the form of
Finally, the "damp" object graph has proxies to the "wet" and "dry"
object graphs, in
In the original membrane designs, you couldn't have a "damp" graph. Nobody really considered it. But there are a few different use-cases I can imagine:
- Different levels of privileges or security clearance, with different API's exposed to the proxies
- A common base set of proxies, and then special extended sets of proxies for specific sections of the object graph (example: shadow content of Web Components should only be available to the code owning the component)
- A function broken up into different phases: precondition,
postcondition, argument validation, main body
- Special case: logging entry and exit
- Special case: class invariants when body doesn't throw
- Think aspect-oriented programming, in a possibly ugly mess
- This doesn't allow code running between lines of a function - at least, I don't see how that would be possible without an interpreter like ye olde Narcissus.
- A "read-only" view of an object graph, where .defineProperty() returns false every time. (As opposed to a hidden or "whitelisted" object graph.)