Next: Property Proxies, Up: The Visibility Object [Contents]
B.2.3.1 Visibility Object Implementation
The visibility object is mostly simply represented in the following diagram:
Specifically, the visibility object is a prototype chain containing the private members of the class associated with the method currently being invoked on the current instance, its protected members (including those inherited from its supertype) and the public members (also including those inherited from the supertype). To accomplish this, the visibility object has the following properties:
- The private object is swappable - that is, it is the only portion of
the prototype chain that is replaced between calls to various methods.
- It is for this reason that the private object is placed atop the prototype
chain. This allows it to be swapped very cheaply by simply passing
different objects to be bound to
this
.
- It is for this reason that the private object is placed atop the prototype
chain. This allows it to be swapped very cheaply by simply passing
different objects to be bound to
- Both the private and protected objects are initialized during instantiation
by the
__initProps()
method attached byClassBuilder
to each class during definition.- Properties are cloned to ensure references are not shared between instances.
- Methods are copied by reference, since their implementations are immutable.
- This must be done because neither protected nor private members may be
added to the prototype chain of a class.
- Doing so would effectively make them public.
- Doing so would also cause private members to be inherited by subtypes.
- Public members are a part of the class prototype chain as you would expect
in any conventional prototype.
- Public properties only are initialized by
__initProps()
, just as private and protected properties, to ensure that no references are shared between instances.
- Public properties only are initialized by
As a consequence of the above, it is then clear that there must be a separate visibility object (prototype chain) for each supertype of each instance, because there must be a separate private object for each subtype of each instance. Let us consider for a moment why this is necessary with the following sample of code:
var C1 = Class( { 'private _name': 'Foo', 'public getName': function() { return this._name; }, // ... } ), // note the naming convention using descending ids for the discussion // following this example C0 = C1.extend( { // ... } ); C1().getName(); // "Foo" C0().getName(); // "Foo"
Figure B.14 demonstrates why the private object swapping21 is indeed necessary. If a subtype does not override a super method that uses a private member, it is important that the private member be accessible to the method when it is called. In Figure B.14, if we did not swap out the object, _name would be undefined when invoked on C2.
Given this new information, the implementation would more formally be represented as a collection of objects V for class C and each of its supertypes as denoted by C\_n, with C\_0 representing the class having been instantiated and any integer n > 0 representing the closest supertype, such that each V\_n is associated with C\_n, V\_n\^x is the visibility object bound to any method associated with class C\_x and each V shares the same prototype chain P\_n for any given instance of C\_n:
Fortunately, as shown in Figure B.15, the majority of the prototype chain can be reused under various circumstances:
- For each instance of class C\_n, P\_n is re-used as the prototype of every V\_n.
- C\_n is re-used as the prototype for each P\_n.
Consequently, on instantiation of class C\_n, we incur a performance
hit from __initProps()
for the initialization of each member of
V\_x and P\_x, as well as each property of C\_x,
recursively for each value of m ≥ x ≥ n (that is,
supertypes are initialized first), where m is equal to the number of
supertypes of class C\_n + 1.22
The instance stores a reference to each of the visibility objects V, indexed by an internal class identifier (which is simply incremented for each new class definition, much like we did with the instance id in Figure B.8). When a method is called, the visibility object that matches the class identifier associated with the invoked method is then passed as the context (bound to this) for that method (see Method Wrapping).
Footnotes
(21)
The term “swapping” can be a bit deceptive. While we are swapping in the sense that we are passing an entirely new private object as the context to a method, we are not removing an object from the prototype chain and adding another in place of it. They do, however, share the same prototype chain.
(22)
There is room for optimization in this implementation, which will be left for future versions of ease.js.
Next: Property Proxies, Up: The Visibility Object [Contents]