A common capability of many dynamic languages, such as JavaScript, is the ability of a program to inspect and modify its own structure. This capability is generally called reflection. Examples of reflective capabilities of JavaScript include things like the hasOwnProperty
and isPrototypeOf
methods. ECMAScript 5 extended the reflection capability to JavaScript via functions such as Object.defineProperty
and Object.getOwnPropertyDescriptor
. There are many reasons you might use reflection but two very common uses are for creating development/debugging tool and for meta-programming.
There are many different ways you might define a reflection API for a programming language. For example, in JavaScript hasOwnProperty
is a method defined by Object.prototype
so it is, in theory, available to be called as a method on all objects. But there is a problem with this approach. What happens if an application object defines its own method named hasOwnPro
perty
? The application object definition will override the definition of hasOwnProperty
that is normally inherited from Object.prototype
. Unexpected results are likely to occur if such an object is passed to code that expects to do reflection using the built-in hasOwnProperty
method. This is one of the reasons that the new reflection capabilities in ES5 are defined as functions on Object
rather than as methods of Object.prototype
.
Another issue that arises with many reflection APIs is that they typically only work with local objects. Consider a tool that gives application developers the ability to graphically browse and inspect the objects in an application. If such a tool is effective, developers might want to use it in other situations. For example, they might want to inspect the objects on a remote server-based JavaScript application or to inspect a diagnostic JSON dump of objects produced when an application crashed. If JavaScript’s existing reflection APIs were used to create the tool there is no direct way it can be used to inspect such objects because the JavaScript reflection APIs only operate upon local objects within the current program.
There is also a tension between the power of reflection and security concerns within applications. Many of the reflection capabilities that are most useful to tool builders and meta-programmers can also be exploited for malicious purposes. Reflection API designers sometimes exclude potentially useful features in order to eliminate the potential of such exploits.
Mirrors is the name of an approach to reflection API design that attempts to address many of the issue that have been encountered with various programming languages that support reflection. The basic idea of mirrors is that you never perform reflective operations directly upon application objects. Instead all such operations are performed upon distinct “mirror” objects that “reflect” the structure of corresponding application objects. For example, instead of coding something like:
if (someObj.hasOwnProperty('customer')) {...
you might accomplish the same thing via mirrors via something like:
if (Mirror.on(someObj).hasOwnProperty('customer')) {...
Mirrors don’t have the sort of issues I discussed above because when using them you never directly reflect on application objects. There is never any problem if the application just happens to define a method that has the same name as a reflection API method. Because reflection-based tools only indirectly interact with the underlying objects via mirror objects, it is possible to create different mirrors that use a common interface to access either local object, remote objects, or static objects stored in a file. Similar, it is possible to have have mirrors that present a common interface but differ in terms how much reflection they allow. A trusted tool might be given access to a mirror that supports the must power reflective operations while an untrusted plug-in might be restricted to using mirrors that support only a limited set of reflective operations.
Gilad Bracha and David Ungar are the authors of a paper that explain the principals behind mirror-based reflection: Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages. I highly recommend it if you are interested in the general topic of reflection.
Mirrors were originally developed for the self programming language, one of the languages that influenced the original design of JavaScript. Recently, I’ve been experimenting with defining a mirror based reflection interface for JavaScript. An early prototype of this interface named jsmirrors is now up on github. It uses a common interface to support reflection on both local JavaScript objects and on a JSON-based object encoding that could be used for remote or externally stored objects. It also supports three levels of reflection privilege.
In my next post I’ll explain more of the usage and design details of jsmirrors. In the meantime, please feel free to take a look at the prototype.
(Photo by “dichohecho”, Creative Commons Attribution License)
Comments on this entry are closed.
Interesting. How are mirrors different from harmony proxies?
See the paper Mirages in AmbientTalk by Mostinchx, Van Cutsem, Timbermont and Tanter for a good discussion of this difference. Main differences, Mirrors are explicit, Proxies are implicit. Mirrors support introspection, evaluation, and mutation. Proxies support intersession — the modification of base language semantics.
This is great stuff. One thing I wonder though is what value there is to attenuating reflection authority without also attenuating ECMAScript’s built-in reflective capabilities? Can a proxy wrapper completely intercede on all the Object.* goodies?
And one little bikeshed: the “on” method has been gaining de facto traction as an event-registration mechanism — perhaps Mirror.of would be better?
All the Object.* properties are writable & configurable so it is currently possible to replace or delete any of those methods. For example you might create an iframe and initially run some reflection attenuation setup before loading any application code for into that iframe. Module loaders in the next edition load ECMAScript will provide a more general way to do this sort of context setup.
Regarding naming. Mirror.for is another possibility. On, of, for…not sure which is best.
That’s good to hear that es-next modules give us something for sandboxing — I didn’t realize. I’d actually started to the sandboxing possibility in my first comment when it occured to me that it woudln’t enough to attenuate all reflection caps. You couldn’t eliminate “in”, “for-in” and any other reflection goodies baked into the lang, so you’d need a proxy regardless — which is why I asked it that way. I guess you could always come at it from both sides but that strikes me as pretty convoluted — it’d be much nicer if proxies could just fully attenuate all reflection. Do you know if they can?