From 0a88a0f83bf01eec22d62182dd5af27c042846b2 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sun, 27 Nov 2011 14:11:02 -0500 Subject: [PATCH] [#5] Added bulk of "The Visibility Object" section to manual --- doc/img/visobj-collection-wide.dia | Bin 0 -> 4374 bytes doc/img/visobj-collection-wide.txt | 18 +++ doc/img/visobj.dia | Bin 0 -> 1935 bytes doc/img/visobj.txt | 7 + doc/impl-details.texi | 209 +++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+) create mode 100644 doc/img/visobj-collection-wide.dia create mode 100644 doc/img/visobj-collection-wide.txt create mode 100644 doc/img/visobj.dia create mode 100644 doc/img/visobj.txt diff --git a/doc/img/visobj-collection-wide.dia b/doc/img/visobj-collection-wide.dia new file mode 100644 index 0000000000000000000000000000000000000000..182d382eef37dcb9909cd70fba656b940f8579bc GIT binary patch literal 4374 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 VgCFine^&V6?mz$M5r}?(005i#(*Xbg literal 0 HcmV?d00001 diff --git a/doc/img/visobj.txt b/doc/img/visobj.txt new file mode 100644 index 0000000..c0015ce --- /dev/null +++ b/doc/img/visobj.txt @@ -0,0 +1,7 @@ +,---------, +| private | [this; swappable; init per instance] +`------,--`--------, + |_| protected | [init per instance] + `-------,---`----, + |_| public | [directly inherited; external API] + `--------` diff --git a/doc/impl-details.texi b/doc/impl-details.texi index f7c5c2c..0b586e2 100644 --- a/doc/impl-details.texi +++ b/doc/impl-details.texi @@ -421,6 +421,7 @@ around them. @menu * Encapsulation In JavaScript:: * Hacking Around the Issue of Encapsulation:: +* The Visibility Object:: @end menu @node Encapsulation In JavaScript @@ -895,6 +896,214 @@ referencing that object directly, like we must do in our methods in private methods. We will explore how this is handled in the following section. +@node The Visibility Object +@subsection The Visibility Object +Let's consider how we may rewrite @var{Stack} in +@ref{f:js-obscure-private-methods} using ease.js: + +@float Figure, f:stack-using-easejs +@verbatim +var Stack = Class( 'Stack', +{ + 'private _data': [], + + 'public push': function( val ) + { + this._data.push( val ); + }, + + 'public pop': function() + { + return this._data.pop(); + } +} ); + +var inst = Stack(); +inst.push( 'foo' ); +inst.pop(); // foo +@end verbatim +@caption{Stack implementation using ease.js} +@end float + +The above implementation is much less impressive looking than our prior +examples. What we have done is encapsulate the excess logic needed to emulate a +class and got right down to business. ease.js will take the class definition +above and generate an object much like we had done in the prior examples, with a +few improvements. + +If you have not read over the previous sections, you are recommended to do so +before continuing in order to better understand the rationale and finer +implementation details. + +The secret behind ease.js's visibility implementation (@pxref{Access +Modifiers}) is referred to internally as the @dfn{visibility object} (or, in +older commits and some notes, the @dfn{property object}). Consider the problem +regarding the verbosity of our private property accessors and method calls in +@ref{f:js-obscure-private-methods}. It would be much more convenient if the +properties and methods were bound to @var{this} so that they can be accessed +more naturally, as would be expected by a programmer familiar with classes in +other Classical Object-Oriented languages (@pxref{f:stack-using-easejs}). This +can be done using @code{call()} or @code{apply()}: + +@float Figure, f:function-context +@verbatim +function getName() +{ + return this.name; +} + +var obj = { name: "foo" }; +getName.call( obj ); // "foo" +@end verbatim +@caption{Calling a function within the context of a given object} +@end float + +@ref{f:function-context} demonstrates the concept we are referring to. Given an +arbitrary object @var{obj}, we can call any given method (in this case, +@code{getName()}, binding @var{this} to that object. This is precisely what +ease.js does with each method call. To understand this process, we have to +explore two concepts: the visibility object itself and method wrapping. + +@subsubsection Visibility Object Implementation +The visibility object is mostly simply represented in the following diagram: + +@float Figure, f:visobj +@image{img/visobj} +@caption{Structure of the visibility object} +@end float + +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: + +@itemize +@item +The private object is @dfn{swappable} - that is, it is the only portion of the +prototype chain that is replaced between calls to various methods. + @itemize + @item + 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 @code{this}. + @end itemize +@item +Both the private and protected objects are initialized during instantiation by +the @code{__initProps()} method attached by @code{ClassBuilder} to each class +during definition. + @itemize + @item + Properties are cloned to ensure references are not shared between instances. + @item + Methods are copied by reference, since their implementations are immutable. + @item + This must be done because neither protected nor private members may be added + to the prototype chain of a class. + @itemize + @item + Doing so would effectively make them public. + @item + Doing so would also cause private members to be inherited by subtypes. + @end itemize + @end itemize +@item +Public members are a part of the class prototype chain as you would expect in +any conventional prototype. + @itemize + @item + Public @emph{properties} only are initialized by @code{__initProps()}, just as + private and protected properties, to ensure that no references are shared + between instances. + @end itemize +@end itemize + +As a consequence of the above, it is then clear that there must be a separate +visibility object (prototype chain) @emph{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: + +@float Figure, f:priv-swap-rationale +@verbatim +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" +@end verbatim +@caption{Why private member swapping is necessary} +@end float + +@ref{f:priv-swap-rationale} demonstrates why the private object +swapping@footnote{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 @emph{not} removing an object from the prototype chain and +adding another in place of it. They @emph{do}, however, share the same prototype +chain.} is indeed necessary. If a subtype does @emph{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 @ref{f:priv-swap-rationale}, if +we did not swap out the object, @var{_name} would be undefined when invoked on +@var{C2}. + +Given this new information, the implementation would more formally be +represented as a collection of objects @var{V} for class @var{C} and each of its +supertypes as denoted by @var{C\_n}, with @var{C\_0} representing the class +having been instantiated and any integer @var{n} >= 0 representing the closest +supertype, such that each @var{V\_n} is associated with @var{C\_n}, +@var{V\_n\^x} is the visibility object bound to any method associated with class +@var{C\_x} and each @var{V} shares the same prototype chain @var{P\_n} for any +given instance of @var{C\_n}: + +@float Figure, f:visobj-collection +@image{img/visobj-collection-wide} +@caption{Collection of visibility objects @var{V} for each class @var{C}} +@end float + +Fortunately, as shown in @ref{f:visobj-collection}, the majority of the +prototype chain can be reused under various circumstances: + +@itemize +@item +For each instance of class @var{C\_n}, @var{P\_n} is re-used as the +prototype of every @var{V\_n}. +@item +@var{C\_n} is re-used as the prototype for each @var{P\_n}. +@end itemize + +Consequently, on instantiation of class @var{C\_n}, we incur a performance hit +from @code{__initProps()} for the initialization of each member of @var{V\_x} +and @var{P\_x}, as well as each property of @var{C\_x}, recursively for each +value of @var{m} >= @var{x} >= @var{n} (that is, supertypes are initialized +first), where @var{m} is equal to the number of supertypes of class @var{C\_n} + +@var{n}.@footnote{There is room for optimization in this implementation, which +will be left for future versions of ease.js.} + +The instance stores a reference to each of the visibility objects @var{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 +@ref{f:js-encapsulate-instance}. 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 @var{this}) for that method. + + @node Internal Methods/Objects @section Internal Methods/Objects There are a number of internal methods/objects that may be useful to developers