Initial commit of working draft, revised

master
Mike Gerwitz 2012-02-15 23:11:23 -05:00
commit 74476ca64a
11 changed files with 2314 additions and 0 deletions

7
.gitignore vendored 100644
View File

@ -0,0 +1,7 @@
*.aux
*.log
*.toc
*.dvi
*.pdf
*.ps
*.out

451
COPYING 100644
View File

@ -0,0 +1,451 @@
GNU Free Documentation License
Version 1.3, 3 November 2008
Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
<http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other
functional and useful document "free" in the sense of freedom: to
assure everyone the effective freedom to copy and redistribute it,
with or without modifying it, either commercially or noncommercially.
Secondarily, this License preserves for the author and publisher a way
to get credit for their work, while not being considered responsible
for modifications made by others.
This License is a kind of "copyleft", which means that derivative
works of the document must themselves be free in the same sense. It
complements the GNU General Public License, which is a copyleft
license designed for free software.
We have designed this License in order to use it for manuals for free
software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the
software does. But this License is not limited to software manuals;
it can be used for any textual work, regardless of subject matter or
whether it is published as a printed book. We recommend this License
principally for works whose purpose is instruction or reference.
1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work, in any medium, that
contains a notice placed by the copyright holder saying it can be
distributed under the terms of this License. Such a notice grants a
world-wide, royalty-free license, unlimited in duration, to use that
work under the conditions stated herein. The "Document", below,
refers to any such manual or work. Any member of the public is a
licensee, and is addressed as "you". You accept the license if you
copy, modify or distribute the work in a way requiring permission
under copyright law.
A "Modified Version" of the Document means any work containing the
Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of
the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall
subject (or to related matters) and contains nothing that could fall
directly within that overall subject. (Thus, if the Document is in
part a textbook of mathematics, a Secondary Section may not explain
any mathematics.) The relationship could be a matter of historical
connection with the subject or with related matters, or of legal,
commercial, philosophical, ethical or political position regarding
them.
The "Invariant Sections" are certain Secondary Sections whose titles
are designated, as being those of Invariant Sections, in the notice
that says that the Document is released under this License. If a
section does not fit the above definition of Secondary then it is not
allowed to be designated as Invariant. The Document may contain zero
Invariant Sections. If the Document does not identify any Invariant
Sections then there are none.
The "Cover Texts" are certain short passages of text that are listed,
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
the Document is released under this License. A Front-Cover Text may
be at most 5 words, and a Back-Cover Text may be at most 25 words.
A "Transparent" copy of the Document means a machine-readable copy,
represented in a format whose specification is available to the
general public, that is suitable for revising the document
straightforwardly with generic text editors or (for images composed of
pixels) generic paint programs or (for drawings) some widely available
drawing editor, and that is suitable for input to text formatters or
for automatic translation to a variety of formats suitable for input
to text formatters. A copy made in an otherwise Transparent file
format whose markup, or absence of markup, has been arranged to thwart
or discourage subsequent modification by readers is not Transparent.
An image format is not Transparent if used for any substantial amount
of text. A copy that is not "Transparent" is called "Opaque".
Examples of suitable formats for Transparent copies include plain
ASCII without markup, Texinfo input format, LaTeX input format, SGML
or XML using a publicly available DTD, and standard-conforming simple
HTML, PostScript or PDF designed for human modification. Examples of
transparent image formats include PNG, XCF and JPG. Opaque formats
include proprietary formats that can be read and edited only by
proprietary word processors, SGML or XML for which the DTD and/or
processing tools are not generally available, and the
machine-generated HTML, PostScript or PDF produced by some word
processors for output purposes only.
The "Title Page" means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the material
this License requires to appear in the title page. For works in
formats which do not have any title page as such, "Title Page" means
the text near the most prominent appearance of the work's title,
preceding the beginning of the body of the text.
The "publisher" means any person or entity that distributes copies of
the Document to the public.
A section "Entitled XYZ" means a named subunit of the Document whose
title either is precisely XYZ or contains XYZ in parentheses following
text that translates XYZ in another language. (Here XYZ stands for a
specific section name mentioned below, such as "Acknowledgements",
"Dedications", "Endorsements", or "History".) To "Preserve the Title"
of such a section when you modify the Document means that it remains a
section "Entitled XYZ" according to this definition.
The Document may include Warranty Disclaimers next to the notice which
states that this License applies to the Document. These Warranty
Disclaimers are considered to be included by reference in this
License, but only as regards disclaiming warranties: any other
implication that these Warranty Disclaimers may have is void and has
no effect on the meaning of this License.
2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies
to the Document are reproduced in all copies, and that you add no
other conditions whatsoever to those of this License. You may not use
technical measures to obstruct or control the reading or further
copying of the copies you make or distribute. However, you may accept
compensation in exchange for copies. If you distribute a large enough
number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and
you may publicly display copies.
3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have
printed covers) of the Document, numbering more than 100, and the
Document's license notice requires Cover Texts, you must enclose the
copies in covers that carry, clearly and legibly, all these Cover
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
the back cover. Both covers must also clearly and legibly identify
you as the publisher of these copies. The front cover must present
the full title with all words of the title equally prominent and
visible. You may add other material on the covers in addition.
Copying with changes limited to the covers, as long as they preserve
the title of the Document and satisfy these conditions, can be treated
as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit
reasonably) on the actual cover, and continue the rest onto adjacent
pages.
If you publish or distribute Opaque copies of the Document numbering
more than 100, you must either include a machine-readable Transparent
copy along with each Opaque copy, or state in or with each Opaque copy
a computer-network location from which the general network-using
public has access to download using public-standard network protocols
a complete Transparent copy of the Document, free of added material.
If you use the latter option, you must take reasonably prudent steps,
when you begin distribution of Opaque copies in quantity, to ensure
that this Transparent copy will remain thus accessible at the stated
location until at least one year after the last time you distribute an
Opaque copy (directly or through your agents or retailers) of that
edition to the public.
It is requested, but not required, that you contact the authors of the
Document well before redistributing any large number of copies, to
give them a chance to provide you with an updated version of the
Document.
4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under
the conditions of sections 2 and 3 above, provided that you release
the Modified Version under precisely this License, with the Modified
Version filling the role of the Document, thus licensing distribution
and modification of the Modified Version to whoever possesses a copy
of it. In addition, you must do these things in the Modified Version:
A. Use in the Title Page (and on the covers, if any) a title distinct
from that of the Document, and from those of previous versions
(which should, if there were any, be listed in the History section
of the Document). You may use the same title as a previous version
if the original publisher of that version gives permission.
B. List on the Title Page, as authors, one or more persons or entities
responsible for authorship of the modifications in the Modified
Version, together with at least five of the principal authors of the
Document (all of its principal authors, if it has fewer than five),
unless they release you from this requirement.
C. State on the Title page the name of the publisher of the
Modified Version, as the publisher.
D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications
adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice
giving the public permission to use the Modified Version under the
terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections
and required Cover Texts given in the Document's license notice.
H. Include an unaltered copy of this License.
I. Preserve the section Entitled "History", Preserve its Title, and add
to it an item stating at least the title, year, new authors, and
publisher of the Modified Version as given on the Title Page. If
there is no section Entitled "History" in the Document, create one
stating the title, year, authors, and publisher of the Document as
given on its Title Page, then add an item describing the Modified
Version as stated in the previous sentence.
J. Preserve the network location, if any, given in the Document for
public access to a Transparent copy of the Document, and likewise
the network locations given in the Document for previous versions
it was based on. These may be placed in the "History" section.
You may omit a network location for a work that was published at
least four years before the Document itself, or if the original
publisher of the version it refers to gives permission.
K. For any section Entitled "Acknowledgements" or "Dedications",
Preserve the Title of the section, and preserve in the section all
the substance and tone of each of the contributor acknowledgements
and/or dedications given therein.
L. Preserve all the Invariant Sections of the Document,
unaltered in their text and in their titles. Section numbers
or the equivalent are not considered part of the section titles.
M. Delete any section Entitled "Endorsements". Such a section
may not be included in the Modified Version.
N. Do not retitle any existing section to be Entitled "Endorsements"
or to conflict in title with any Invariant Section.
O. Preserve any Warranty Disclaimers.
If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all
of these sections as invariant. To do this, add their titles to the
list of Invariant Sections in the Modified Version's license notice.
These titles must be distinct from any other section titles.
You may add a section Entitled "Endorsements", provided it contains
nothing but endorsements of your Modified Version by various
parties--for example, statements of peer review or that the text has
been approved by an organization as the authoritative definition of a
standard.
You may add a passage of up to five words as a Front-Cover Text, and a
passage of up to 25 words as a Back-Cover Text, to the end of the list
of Cover Texts in the Modified Version. Only one passage of
Front-Cover Text and one of Back-Cover Text may be added by (or
through arrangements made by) any one entity. If the Document already
includes a cover text for the same cover, previously added by you or
by arrangement made by the same entity you are acting on behalf of,
you may not add another; but you may replace the old one, on explicit
permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License
give permission to use their names for publicity for or to assert or
imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this
License, under the terms defined in section 4 above for modified
versions, provided that you include in the combination all of the
Invariant Sections of all of the original documents, unmodified, and
list them all as Invariant Sections of your combined work in its
license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and
multiple identical Invariant Sections may be replaced with a single
copy. If there are multiple Invariant Sections with the same name but
different contents, make the title of each such section unique by
adding at the end of it, in parentheses, the name of the original
author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of
Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History"
in the various original documents, forming one section Entitled
"History"; likewise combine any sections Entitled "Acknowledgements",
and any sections Entitled "Dedications". You must delete all sections
Entitled "Endorsements".
6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other
documents released under this License, and replace the individual
copies of this License in the various documents with a single copy
that is included in the collection, provided that you follow the rules
of this License for verbatim copying of each of the documents in all
other respects.
You may extract a single document from such a collection, and
distribute it individually under this License, provided you insert a
copy of this License into the extracted document, and follow this
License in all other respects regarding verbatim copying of that
document.
7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate
and independent documents or works, in or on a volume of a storage or
distribution medium, is called an "aggregate" if the copyright
resulting from the compilation is not used to limit the legal rights
of the compilation's users beyond what the individual works permit.
When the Document is included in an aggregate, this License does not
apply to the other works in the aggregate which are not themselves
derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these
copies of the Document, then if the Document is less than one half of
the entire aggregate, the Document's Cover Texts may be placed on
covers that bracket the Document within the aggregate, or the
electronic equivalent of covers if the Document is in electronic form.
Otherwise they must appear on printed covers that bracket the whole
aggregate.
8. TRANSLATION
Translation is considered a kind of modification, so you may
distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special
permission from their copyright holders, but you may include
translations of some or all Invariant Sections in addition to the
original versions of these Invariant Sections. You may include a
translation of this License, and all the license notices in the
Document, and any Warranty Disclaimers, provided that you also include
the original English version of this License and the original versions
of those notices and disclaimers. In case of a disagreement between
the translation and the original version of this License or a notice
or disclaimer, the original version will prevail.
If a section in the Document is Entitled "Acknowledgements",
"Dedications", or "History", the requirement (section 4) to Preserve
its Title (section 1) will typically require changing the actual
title.
9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense, or distribute it is void, and
will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, receipt of a copy of some or all of the same material does
not give you any rights to use it.
10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions of the
GNU Free Documentation License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns. See
http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number.
If the Document specifies that a particular numbered version of this
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that specified version or
of any later version that has been published (not as a draft) by the
Free Software Foundation. If the Document does not specify a version
number of this License, you may choose any version ever published (not
as a draft) by the Free Software Foundation. If the Document
specifies that a proxy can decide which future versions of this
License can be used, that proxy's public statement of acceptance of a
version permanently authorizes you to choose that version for the
Document.
11. RELICENSING
"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
World Wide Web server that publishes copyrightable works and also
provides prominent facilities for anybody to edit those works. A
public wiki that anybody can edit is an example of such a server. A
"Massive Multiauthor Collaboration" (or "MMC") contained in the site
means any set of copyrightable works thus published on the MMC site.
"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
license published by Creative Commons Corporation, a not-for-profit
corporation with a principal place of business in San Francisco,
California, as well as future copyleft versions of that license
published by that same organization.
"Incorporate" means to publish or republish a Document, in whole or in
part, as part of another Document.
An MMC is "eligible for relicensing" if it is licensed under this
License, and if all works that were first published under this License
somewhere other than this MMC, and subsequently incorporated in whole or
in part into the MMC, (1) had no cover texts or invariant sections, and
(2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site
under CC-BY-SA on the same site at any time before August 1, 2009,
provided the MMC is eligible for relicensing.
ADDENDUM: How to use this License for your documents
To use this License in a document you have written, include a copy of
the License in the document and put the following copyright and
license notices just after the title page:
Copyright (c) YEAR YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
replace the "with...Texts." line with this:
with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts, or some other
combination of the three, merge those two alternatives to suit the
situation.
If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of
free software license, such as the GNU General Public License,
to permit their use in free software.

15
Makefile 100644
View File

@ -0,0 +1,15 @@
# This file is under the public domain.
.PHONY: default pdf
.SUFFIXES: .tex .pdf
default: pdf
pdf: coope.pdf
# intentionally two-pass
.tex.pdf:
pdflatex $< $@
pdflatex $< $@
clean:
rm -f coope.pdf

15
README 100644
View File

@ -0,0 +1,15 @@
This article is free; see COPYING for additional information.
This article, entitled "Classical Object-Oriented Programming with ECMAScript",
summarizes much of the research performed during the development of ease.js, as
classical object-oriented framework for ECMAScript/JavaScript. While the
information contained within the article is expressed in the author's own words,
an effort has been made to be as professional as possible to promote
modification without much distinction.
It is the author's hope that this article will continue to be updated with
additional research as the project grows. Contributors to ease.js can feel free
to update the document with information that is pertinant. Likewise, individuals
who have a strong method of achieving an object-oriented style in ECMAScript can
feel free to contribute their own ideas, so long as it does not add considerable
bloat or muddy the point of the article.

22
abstract.tex 100644
View File

@ -0,0 +1,22 @@
\abstract
ECMAScript (more popularly known by the name ``JavaScript'') is the language of
the web. In the decades past, it has been used to augment web pages with trivial
features and obnoxious gimmicks. Today, the language is used to write
full-featured web applications that rival modern desktop software in nearly
every regard and has even expanded to create desktop and server software. With
increased usage, there is a desire to apply more familiar development paradigms
while continuing to take advantage of the language's incredibly flexible
functional and prototypal models. Of all of the modern paradigms, one of the
most influential and widely adopted is Classical Object-Oriented programming, as
represented in languages such as Java, C++, Python, Perl, PHP and others.
ECMAScript, as an object-oriented language, contains many features familiar to
Classical OO developers. However, certain features remain elusive. This article
will detail the development of a classical object-oriented framework for
ECMAScript, ease.js, which aims to address these issues by augmenting
ECMAScript's prototype model to allow the creation of familiar class-like
objects. This implementation enforces encapsulation and provides features that
most Classical OO developers take for granted until the time that ECMAScript
implements these features itself.\footnote{There was discussion of including
classes in ECMAScript 6 ``Harmony'', however it is not within the specification
at the time of writing. At that time, the framework could be used to transition
to ECMAScript's model, should the developer choose to do so.}

82
coope.sty 100644
View File

@ -0,0 +1,82 @@
% COOPE style
\usepackage{graphicx}
\usepackage[small,bf,justification=justified,singlelinecheck=false]{caption}
\usepackage[margin=.7in]{geometry}
\usepackage{listings}
\usepackage{color}
\usepackage{hyperref}
\hypersetup{colorlinks,
citecolor=black,
filecolor=black,
linkcolor=black,
urlcolor=black,
bookmarksopen=true,
pdftex}
\definecolor{gray}{rgb}{0.3,0.3,0.3}
\definecolor{darkgray}{rgb}{0.2,0.2,0.2}
\definecolor{lightgray}{rgb}{0.6,0.6,0.6}
\newcommand{\jsref}{\textmd Listing~\ref}
\lstdefinelanguage{JavaScript}{%
keywords={%
undefined,null,true,false,NaN,Infinity,return,%
try,catch,finally,function,var,if,then,else,%
in,while,do,done,case,break,continue%
},
keywordstyle=\color{gray},
comment=[l]{//},
morecomment=[s]{/*}{*/},
commentstyle=\color{lightgray}
}
\lstset{%
language=JavaScript,
aboveskip=12pt,
columns=fullflexible,
keepspaces=true,
basicstyle=\small\ttfamily,
xleftmargin=18pt,
captionpos=b,
abovecaptionskip=12pt,
numbers=left,
numbersep=10pt,
numberstyle=\small\color{darkgray}
}
\newenvironment{jsex}[2]
{%
\begin{lstlisting}
\captionof{#2}
\label{#1}
}
{%
\end{lstlisting}
}
\newenvironment{jsexspan}[2]
{%
\begin{js*}
\caption{#2}
\label{lst:#1}
\begin{multicols}{2}
\columnwidth 10in
\small
}
{%
\end{multicols}
\end{js*}
}
\newcommand{\dfn}{\emph}
\newcommand{\var}{\texttt}
\newcommand{\code}{\texttt}
\newcommand{\keyword}{\texttt}
\newcommand{\operator}{\texttt}
\newcommand{\func}{\texttt}
\hyphenation{ECMA-Script Java-Script}

44
coope.tex 100644
View File

@ -0,0 +1,44 @@
% Classical Object-Oriented Programming with ECMAScript
%
% Copyright (C) 2012 Mike Gerwitz
%
% Permission is granted to copy, distribute and/or modify this document under
% the terms of the GNU Free Documentation License, Version 1.3 or any later
% version published by the Free Software Foundation; with no Invariant
% Sections, no Front-Cover Texts and no Back-Cover Texts. A copy of the license
% is included in the section entitled "GNU Free Documentation License".
%%
\documentclass[twocolumn]{article}
\input coope.sty
\author{Mike Gerwitz}
\date{\today}
\begin{document}
\centerline{\Large \bf Classical Object-Oriented}
\centerline{\Large \bf Programming with ECMAScript}
\medskip
\centerline{\bf Mike Gerwitz}
\medskip
\centerline{\today}
\centerline{(Working Draft)}
% Contributors: uncomment the following two lines and add your name(s). Please
% do not add yourself as an author unless you author a substantial portion of
% the text.
%\medskip
%\centerline{\bf{Contributors:} \textnormal{None}}
\medskip
\input{abstract}
\tableofcontents
\input{sec/class-like}
\input{sec/hacking-proto}
\input{sec/encap-hacks}
\input{sec/licenses}
\end{document}

543
sec/class-like.tex 100644
View File

@ -0,0 +1,543 @@
\section{Class-Like Objects in ECMAScript}
\label{sec:class-like}
JavaScript is a multi-paradigm scripting language standardized by ECMAScript,
incorporating object-oriented, functional and imperative styles. The
Object-Oriented paradigm in itself supports two sub-paradigms - prototypal and
classical, the latter of which is popular in languages such as Java, C++,
Python, Perl, Ruby, Lisp, PHP, Smalltalk, among many others. ECMAScript itself
is prototypal.
The creation of objects in ECMAScript can be as simple as using an object
literal, as defined by curly braces:
\begin{verbatim}
var obj = { foo: "bar" };
\end{verbatim}
In a classical sense, object literals can be thought of as anonymous
singletons;\footnote{GOF.} that is, they have no name (they are identified by
the variable to which they are assigned) and only one instance of the literal
will exist throughout the life of the software.\footnote{Technically, one could
set the prototype of a constructor to be the object defined by the literal (see
\jsref{lst:proto-reuse}), however the resulting instances would be prototypes,
not instances of a common class shared by the literal and each subsequent
instance.} For example, calling a function that returns the same object literal
will return a distinct, entirely unrelated object for each invocation:
\begin{verbatim}
function createObj()
{
return { name: "foo" };
}
createObj() !== createObj();
\end{verbatim}
Using this method, we can create basic objects that act much like class
instances, as demonstrated in \jsref{lst:singleton}:
\begin{lstlisting}[%
label=lst:singleton,
caption=A ``singleton'' with properties and methods
]
var obj = {
name: "Foo",
setName: function( val )
{
obj.name = val;
},
getName: function()
{
return obj.name;
}
};
obj.getName(); // "Foo"
obj.setName( "Bar" );
obj.getName(); // "Bar"
\end{lstlisting}
\subsection{Prototypes}
\label{sec:proto}
We could re-use \var{obj} in \jsref{lst:singleton} as a \dfn{prototype},
allowing instances to inherit its members. For example:
\begin{lstlisting}[%
label=lst:proto-reuse,
caption=Re-using objects as prototyes \bf{(bad)},
firstnumber=last
]
function Foo() {}
Foo.prototype = obj;
var inst1 = new Foo(),
inst2 = new Foo();
inst2.setName( "Bar" );
inst1.getName(); // "Bar"
inst2.getName(); // "Bar"
\end{lstlisting}
In \jsref{lst:proto-reuse} above, we define \var{Foo} to be a
\dfn{constructor}\footnote{A ``constructor'' in ECMAScript is simply any
function intended to be invoked, often (but not always) with the \operator{new}
operator, that returns a new object whose members are derived from the
function's \var{prototype} property.} with our previous object \var{obj} as its
prototype. Unfortunately, as shown in \jsref{lst:singleton}, \var{name} is being
set on \var{obj} itself, which is a prototype shared between both instances.
Setting the name on one object therefore changes the name on the other (and,
indeed, all instances of \var{Foo}). To illustrate this important concept,
consider \jsref{lst:proto-mod} below, which continues from
\jsref{lst:proto-reuse}:
\begin{lstlisting}[%
label=lst:proto-mod,
caption=The effect of prototypes on instances,
firstnumber=last
]
obj.foo = "bar";
inst1.foo; // "bar"
inst2.foo; // "bar"
\end{lstlisting}
Clearly, this is not how one would expect class-like objects to interact; each
object is expected to have its own state. When accessing a property of an
object, the members of the object itself are first checked. If the member
is not defined on the object itself,\footnote{Note that ``not defined'' does not
imply \emph{undefined}; \code{undefined} is a value.} then the prototype chain
is traversed. Therefore, we can give objects their own individual state by
defining the property on the individual instances, rather than the prototype, as
shown in \jsref{lst:inst-prop}.\footnote{Also demonstrated in
\jsref{lst:inst-prop} is the effect of the \keyword{delete} keyword, which
removes a member from an object, allowing the values of the prototype to ``peek
through`` as if a hole exists in the object. Setting the value to
\code{undefined} will not have the same effect, as it does not produce the
``hole''; the property would return \code{undefined} rather than the value on
the prototype.}
\begin{lstlisting}[%
label=lst:inst-prop,
caption=Setting properties per-instance,
firstnumber=last
]
inst1.foo = "baz";
inst1.foo; // "baz"
inst2.foo; // "bar"
delete inst1.foo;
inst1.foo; // "bar"
\end{lstlisting}
This does not entirely solve our problem. As shown in \jsref{lst:singleton}, our
\var{obj} prototype's methods (\func{getName()} and \func{setName()}) reference
\code{obj.name} - our prototype. \jsref{lst:proto-ref} demonstrates the problem
this causes when attempting to give each instance its own state in regards to
the \var{name} property:
\begin{lstlisting}[%
label=lst:proto-ref,
caption=Referencing prototype values in \var{obj} causes problems with
per-instance data,
firstnumber=last
]
// ...
inst1.name = "My Name";
inst1.getName(); // "Foo"
\end{lstlisting}
ECMAScript solves this issue with the \keyword{this} keyword. When a
method\footnote{A \dfn{method} is simply an invokable property of an object (a
function).} of an instance's prototype is invoked, \keyword{this} is bound, by
default,\footnote{One can override this default behavior with
\func{Function.call()} or \func{Function.apply()}.} to a reference of that
instance. Therefore, we can replace \var{obj} in \jsref{lst:singleton} with the
prototype definition in \jsref{lst:proto-proper} to solve the issue
demonstrated in \jsref{lst:proto-ref}:
\begin{lstlisting}[%
label=lst:proto-proper,
caption=Re-using objects as prototypes \bf{(good)}
]
function Foo( name )
{
this.name = name;
};
Foo.prototype = {
setName = function( name )
{
this.name = name;
},
getName = function()
{
return this.name;
}
};
var inst = new Foo( "Bar" );
inst.name; // "Bar"
inst.getName(); // "Bar"
inst.setName( "Baz" );
inst.getName(); // "Baz"
inst.name = "Foo";
inst.getName(); // "Foo"
\end{lstlisting}
\jsref{lst:proto-proper} shows that \keyword{this} is also bound to the new
instance from within the constructor; this allows us to initialize any
properties on the new instance before it is returned to the caller.\footnote{It
is worth mentioning that one can explicitly return an object from the
constructor, which will be returned in place of a new instance.} Evaluation of
the example yields an additional concern --- the observation that all object
members in ECMAScript are public.\footnote{That is not to say that encapsulation
is not possible; this statement is merely emphasizing that properties of objects
do not support access modifiers. We will get into the topic of encapsulation a
bit later.} Even though the \var{name} property was initialized within the
constructor, it is still accessible outside of both the constructor and the
prototype. Addressing this concern will prove to be an arduous process that
will be covered at great length in the following sections. For the time being,
we will continue discussion of conventional techniques, bringing us to the
concept of \dfn{privileged members}.
\subsection{Privileged Members}
\label{sec:privileged}
The concept of \dfn{encapsulation} is a cornerstone of classical object-oriented
programming. Unfortunately, as \jsref{lst:proto-proper} demonstrates, it becomes
difficult to encapsulate data if all members of a given object are accessible
publicly. One means of addressing this issue is to take advantage of the fact
that functions introduce scope, allowing us to define a local variable (or use
an argument) within the constructor that is only accessible to the
\dfn{privileged member} \func{getName()}.
\begin{lstlisting}[%
label=lst:privileged,
caption=Using privileged members to encapsulate data
]
function Foo( name )
{
this.getName = function()
{
return name;
};
this.setName = function( newname )
{
name = newname;
};
}
\end{lstlisting}
If \var{name} in \jsref{lst:privileged} is encapsulated within the constructor,
our methods that \emph{access} that encapsulated data must \emph{too} be
declared within the constructor;\footnote{One may mix prototypes and privileged
members.} otherwise, if placed within the prototype, \var{name} would be out of
scope. This implementation has an unfortunate consequence --- our methods are
now being \emph{redeclared} each and every time a new instance of \var{Foo} is
created, which has obvious performance penalties.\footnote{As a general rule
of thumb, one should only use privileged members for methods that access
encapsulated data; all other members should be part of the prototype.}
Due to these performance concerns, it is often undesirable to use privileged
members; many developers will instead simply prefix with an underscore members
intended to be private (e.g. \code{this.\_name}) while keeping all methods on
the prototype.\footnote{One example of a library that uses underscores in place
of privileged members is Dojo at http://dojotoolkit.org.} This serves as a clear
indicator that the API is not public, is subject to change in the future and
should not be touched. It also allows the property to be accessed by
subtypes,\footnote{The term ``subtype'' is not truly the correct term here.
Rather, the term in this context was meant to imply that an instance of the
constructor was used as the prototype for another constructor, acting much like
a subtype (child class).} acting like a protected member. Unfortunately, this
does not encapsulate the data, so the developer must trust that the user will
not tamper with it.
\subsection{Subtypes and Polymorphism}
In classical terms, \dfn{subtyping} (also known as \dfn{subclassing}) is the act of
extending a \dfn{supertype} (creating a \dfn{child} class from a \dfn{parent})
with additional functionality. The subtype is said to \dfn{inherit} its members
from the supertype.\footnote{In the case of languages that support access
modifiers, only public and protected members are inherited.} Based on our prior
examples in section~\ref{sec:proto}, one could clearly see how the prototype of
any constructor could be replaced with an instance of another constructor,
indefinitely, to achieve an inheritance-like effect. This useful consequence of
the prototype model is demonstrated in \jsref{lst:subtype}.\footnote{Unfortunately, a
responsible implementation is not all so elegant in practice.}
\begin{lstlisting}[%
label=lst:subtype,
caption=Extending prototypes (creating subtypes) in ECMAScript
]
var SubFoo = function( name )
{
// call parent constructor
Foo.call( this, name );
};
SubFoo.prototype = new Foo();
SubFoo.prototype.constructor = SubFoo;
// build upon (extend) Foo
SubFoo.prototype.hello = function()
{
return "Hello, " + this.name;
};
var inst = new SubFoo( "John" );
inst.getName(); // "John"
inst.hello(); // "Hello, John"
\end{lstlisting}
Consider the implications of \jsref{lst:subtype} with a close eye. This
extension of \var{Foo} is rather verbose. The first (and rather unpleasant fact
that may be terribly confusing to those fairly inexperienced with ECMAScript)
consideration to be made is \var{SubFoo}'s constructor. Note how the supertype
(\var{Foo}) must be invoked \emph{within the context of
\var{SubFoo}}\footnote{If \func{Function.call()} or \func{Function.apply()} are
not properly used, the function will, depending on the environment, assign
\keyword{this} to the global scope, which is absolutely not what one wants. In
strict mode, this effect is mitigated, but the result is still not what we
want.} in order to initialize the variables.\footnote{If the constructor accepts
more than a few arguments, one could simply do: \code{Foo.apply( this, arguments
);}} However, once properly deciphered, this call is very similar to invocation
of parent constructors in other languages.
Following the definition of \var{SubFoo} is its prototype (line 6). Note from
section~\ref{sec:proto} that the prototype must contain the members that are to
be accessible to any instances of the constructor. If we were to simply assign
\var{Foo} to the prototype, this would have two terrible consequences, the
second of which will be discussed shortly. The first consequence would be that
all members of \var{Foo} \emph{itself} would be made available to instances of
\var{SubFoo}. In particular, you would find that \code{( new SubFoo()
).prototype === Foo.prototype}, which is hardly your intent. As such, we must
use a new instance of \var{Foo} for our prototype, so that the prototype
contains the appropriate members.
We follow the prototype assignment with another alien declaration --- the
setting of \code{SubFoo.prototype.constructor} on line 7. To understand why this
is necessary, one must first understand that, given any object \var{o} such that
\code{var o = new O()}, \code{o.constructor === O}.\footnote{One could apply
this same concept to other core ECMAScript objects. For example, \code{(
function() \{\}).constructor === Function}, \code{[].constructor === Array},
\code{\{\}.constructor === Object}, \code{true.constructor === Boolean} and
sofourth.} Recall from section~\ref{sec:proto} that values ``peek through
holes'' in the prototype chain. In this case, without our intervention,
\code{SubFoo.prototype.constructor === Foo} because \code{SubFoo.prototype = new
Foo()}. The \var{constructor} property is useful for reflection, so it is
important that we properly set this value to the appropriate constructor ---
\var{SubFoo}. Since \var{SubFoo.prototype} is an \emph{instance} of \var{Foo}
rather than \var{Foo} itself, the assignment will not directly affect \var{Foo}.
This brings us to our aforementioned second consequence of assigning
\code{SubFoo.prototype} to a \emph{new} instance of \var{Foo} --- extending the
prototype by adding to or altering existing values would otherwise change the
supertype's constructor, which would be an unintentional side-effect
that could have drastic consequences on the software.
As an example of extending the prototype (we have already demonstrated
overwriting the \var{constructor} and this concept can be applied to overriding
any members of the supertype), method \var{hello()} has been included in
\jsref{lst:subtype} on line 10. Note that \keyword{this} will be bound to the
instance that the method is being invoked upon, since it is referenced within
the prototype. Also note that we are assigning the function in a slightly
different manner than in \jsref{lst:proto-proper}; this is necessary to ensure
that we do not overwrite the prototype we just declared. Any additional members
must be declared explicitly in this manner, which has the negative consequence
of further increasing the verbosity of the code.
An instance of a subtype can be used in place of any of its supertypes in a
concept known as \dfn{polymorphism}. \jsref{lst:poly} demonstrates this concept
with \func{getFooName()}, a function that will return the name of any object of
type \var{Foo}.\footnote{Please note that the \operator{typeof} operator is not
appropriate in this situation, as both instances of \var{Foo} and \var{SubFoo}
would be considered typeof ``object''. The \operator{instanceof} operator is
appropriate when determining types of objects in terms of their
constructor.}
\begin{lstlisting}[%
label=lst:poly,
caption=Polymorphism in ECMAScript
]
function getFooName( foo )
{
if ( !( foo instanceof Foo ) )
{
throw TypeError(
"Expected instance of Foo"
);
}
return foo.getName();
}
var inst_parent = new Foo( "Parent" ),
inst_child = new SubFoo( "Child" );
getFooName( inst_parent ); // "Parent"
getFooName( inst_child ); // "Child"
getFooName( {} ); // throws TypeError
\end{lstlisting}
The concepts demonstrated in this section could be easily used to extend
prototypes indefinitely, creating what is called a \dfn{prototype chain}. In the
case of an instance of \var{SubFoo}, the prototype chain of most environments
would likely be: \var{SubFoo}, \var{Foo}, \var{Object} (that is,
\code{Object.getPrototypeOf( new SubFoo() ) === SubFoo}, and so
fourth).\footnote{ECMAScript 5 introduces \code{Object.getPrototypeOf()}, which
allows retrieving the prototype of an object (instance). Some environments also
support the non-standard \var{\_\_proto\_\_} property, which is a JavaScript
extension.} Keep in mind, however, that the further down the prototype chain the
engine must traverse in order to find a given member, the greater the
performance impact.
Due to the method used to ``extend'' prototypes, it should also be apparent that
multiple inheritance is unsupported by ECMAScript, as each each constructor may
only have one \var{prototype} property.\footnote{Multiple inheritance is
well-known for its problems. As an alternative, styles of programming similar to
the use of interfaces and traits/mixins in other languages are recommended and
are possible in ECMAScript.}
\subsubsection{Extensible Constructors}
\label{sec:ext-ctor}
Before moving on from the topic of extending prototypes, the assignment of
\code{SubFoo.prototype} deserves some additional discussion. Consider the
implications of this assignment; particularity, the invocation of the
constructor \var{Foo}. ECMAScript does not perform assignments to prototypes
differently than any other assignment, meaning all the logic contained within
the constructor \var{Foo} will be executed. In our case, this does not have any
terrible consequences --- \var{name} will simply be initialized to
\code{undefined}, which will be overridden once \var{SubType} is invoked.
However, consider what may happen if \var{Foo} performed checks on its
arguments.
\begin{lstlisting}[%
label=lst:ctor-problem,
caption=Potential constructor problems for prototype assignments
]
function Foo( name )
{
if ( typeof name !== 'string' )
{
throw TypeError( "Invalid name" );
}
this.name = name;
}
// ...
SubFoo.prototype = new Foo(); // TypeError
\end{lstlisting}
As \jsref{lst:ctor-problem} shows, we can no longer use a new instance of
\var{Foo} as our prototype, unless we were to provide dummy data that will pass
any type checks and validations that the constructor performs. Dummy data is not
an ideal solution --- it muddies the code and will cause subtypes to break
should any validations be added to the supertype in the future.\footnote{Of
course, if the constructor of the supertype changes, there are always BC
(backwards-compatibility) concerns. However, in the case of validations in the
constructor, they may simply enforce already existing docblocks, which should
have already been adhered to.} Furthermore, all constructor logic will still be
performed. What if \var{Foo} were to do something considerably more intensive
--- perform vigorous data validations or initialize a database connection,
perhaps?\footnote{Constructors should take care in limiting what actions they
perform, especially if they produce side-effects.} Not only would we have to
provide potentially complicated dummy data or dummy/stubbed objects, our
prototype assignment would also incur an unnecessary performance hit. Indeed,
the construction logic would be performed \(n + 1\) times --- once for the
prototype and once for each instance, which would overwrite the results of the
previous constructor (or duplicate, depending on implementation).
How one goes about solving this problem depends on the needs of the constructor.
Let us first consider a very basic solution --- ignoring constructor logic if
the provided argument list is empty, as is demonstrated in
\jsref{lst:ctor-ignore-empty}.
\begin{lstlisting}[%
label=lst:ctor-ignore-empty,
caption=Ignoring construction logic if provided with an empty argument list
]
function Foo( name )
{
if ( arguments.length === 0 )
{
return;
}
// ...
this.name = name;
}
// ...
SubType.prototype = new Foo(); // OK
\end{lstlisting}
This solution has its own problems. The most apparent issue is that one could
simply omit all constructor arguments to bypass constructor logic, which is
certainly undesirable.\footnote{Constructors allow us to initialize our object,
placing it in a consistent and predictable state. Allowing a user to bypass this
logic could not only introduce unintended consequences during the life of the
object, but would mandate additional checks during method calls to ensure the
current state is sane, which will add unnecessary overhead.} Secondly --- what
if \var{Foo}'s \var{name} parameter was optional and additional construction
logic needed to be performed regardless of whether or not \var{name} was
provided? Perhaps we would want to provide a default value for \var{name} in
addition to generating a random hash that can be used to uniquely identify each
instance of \var{Foo}. If we are immediately returning from the constructor when
all arguments are omitted, then such an implementation is not possible. Another
solution is needed in this case.\footnote{That is not to say that our first
solution --- immediately returning if no arguments are provided --- is useless.
This is a commonly used method that you may find useful for certain
circumstances.}
A solution that satisfies all needs involves a more complicated hack that we
will defer to section~\ref{sec:extending}.\footnote{One may ask why, given all of
the complications of extending prototypes, one doesn't simply set
\code{SubFoo.prototype = Foo.prototype}. The reason for this is simple --- we
would not be able to extend the prototype without modifying the original, as
they would share references to the same object.}
\subsection{Shortcomings}
ECMAScript's prototype model is highly flexible, but leaves much to be desired:
\begin{description}
\item[Access Modifiers]
Classical OOP permits, generally, three common access modifiers: public,
protected and private. These access modifiers permit encapsulating data that
is unique \emph{per instance} of a given type, without the performance
penalties of privileged members (see \jsref{lst:privileged}).
Not only are access modifiers unsupported, but the concept of protected
members is difficult difficult in ECMAScript. In order for a member to be
accessible to other objects higher up on the prototype chain (``subtypes''),
they must be public. Using privileged members would encapsulate the data
within the constructor, forcing the use of public methods to access the data
and disallowing method overrides, effectively destroying any chances of a
protected API.\footnote{As ease.js will demonstrate, protected APIs are
possible through a clever hack that would otherwise lead to terrible,
unmaintainable code.}
\item[Intuitive Subtyping]
Consider the verbosity of \jsref{lst:subtype}. Now imagine how much
duplicate code is required to maintain many subtypes in a large piece of
software. This only serves to distract developers from the actual business
logic of the prototype, forcing them to think in detailed terms of
prototypes rather than in terms of the problem domain.\footnote{The ability
to think within the problem domain rather than abstract machine concepts is
one of the key benefits of classical object-oriented programming.}
Furthermore, as discussed in section~\ref{sec:ext-ctor}, creating extensible
constructors requires considerable thought that must be handled on a
case-by-case basis, or requires disproportionately complicated hacks (as
will be demonstrated in section~\ref{sec:extending}).
\end{description}
Fortunately,\footnote{Well, fortunately in the sense that ECMAScript is flexible
enough that we can work around the issues. It is, however, terribly messy. In
ECMAScript's defense --- this is a consequence of the prototypal model; our
desire to use class-like objects instead of conventional prototypes produces the
necessity for these hacks.} those issues can be worked around with clever hacks,
allowing us to continue closer toward a classical development model.

131
sec/encap-hacks.tex 100644
View File

@ -0,0 +1,131 @@
\section{Encapsulating the Hacks}
Imagine jumping into a project in order to make a simple modification and then
seeing the code in \jsref{lst:prot-share}. This is a far cry from the simple
protected member declarations in traditional classical object-oriented
languages. In fact, there becomes a point where the hacks discussed in the
previous sections become unmaintainable messes that add a great deal of
boilerplate code with little use other than to distract from the actual
software itself.
However, we do not have to settle for those messy implementations. Indeed, we
can come up with some fairly elegant and concise solutions by encapsulating the
hacks we have discussed into a classical object-oriented framework, library or
simple helper functions. Let's not get ahead of ourselves too quickly; we will
start exploring basic helper functions before we deal with diving into a full,
reusable framework.
This section is intended for educational and experimental purposes. Before using
these examples to develop your own class system for ECMAScript, ensure that none
of the existing systems satisfy your needs; your effort is best suited toward
the advancement of existing projects than the segregation caused by the
introduction of additional, specialty frameworks.\footnote{That is not to
discourage experimentation. Indeed, one of the best, most exciting and fun ways
to learn about these concepts are to implement them yourself.} These are
discussed a bit later.
\subsection{Constructor/Prototype Factory}
\label{sec:ctor-factory}
Section~\ref{sec:extending} offered one solution to the problem of creating a an
extensible constructor, allowing it to be used both to instantiate new objects
and as a prototype. Unfortunately, as \jsref{lst:ctor-extend} demonstrated, the
solution adds a bit of noise to the definition that will also be duplicated for
each constructor. The section ended with the promise of a cleaner, reusable
implementation. Perhaps we can provide that.
Consider once again the issue at hand. The constructor, when called
conventionally with the \keyword{new} keyword to create a new instance, must
perform all of its construction logic. However, if we wish to use it as a
prototype, it is unlikely that we want to run \emph{any} of that logic --- we
are simply looking to have an object containing each of its members to use as a
prototype without the risk of modifying the prototype of the constructor in
question. Now consider how this issue is handled in other classical languages:
the \keyword{extend} keyword.
ECMAScript has no such keyword, so we will have to work on an implementation
ourselves. We cannot use the name \func{extend()}, as it is a reserved
name;\footnote{Perhaps for future versions of ECMAScript.} as such, we will
start with a simple \func{Class} factory function with which we can create new
``classes'' without supertypes. We can than provide a \func{Class.extend()}
method to define a ``class'' with a supertype.
\begin{lstlisting}[%
label=lst:ctor-factory,
caption=Constructor factory
]
var Class = ( function( extending )
{
var C = function( dfn )
{
// extend from an empty base
return C.extend( null, dfn );
};
C.extend = function( base, dfn )
{
base = base || function() {};
var ctor = function()
{
// do nothing if extending
if ( extending )
{
return;
}
// call "actual" constructor
this.__construct &&
this.__construct.apply(
this, arguments
);
};
ctor.prototype = new base();
ctor.prototype.constructor = ctor;
copyTo( ctor.prototype, dfn );
return ctor;
};
function copyTo( dest, members )
{
var hasOwn = Object.prototype
.hasOwnProperty;
for ( var member in members )
{
if ( !hasOwn.call( members, member ) )
{
continue;
}
dest[ member ] = members[ member ];
}
};
return C;
} )( false );
var Foo = Class(
{
__construct: function( name, ignore )
{
ignore || throw Error( "Ctor called" );
this._name = ''+( name );
},
getName: function()
{
return this._name;
}
} );
var SubFoo = Class.extend( Foo,
{
setName: function( name )
{
this._name = ''+( name );
}
} );
\end{lstlisting}

View File

@ -0,0 +1,956 @@
\section{Hacking Around Prototypal Limitations}
Section~\ref{sec:class-like} demonstrated how one would work within the
limitations of conventional ECMAScript to produce class-like objects using
prototypes. For those coming from other classical object-oriented languages,
these features are insufficient. In order to address many of the remaining
issues, more elaborate solutions are necessary.
It should be noted that all the hacks in this section will, in some way or
another, introduce additional overhead, although it should be minimal in
comparison with the remainder of the software that may implement them.
Performance considerations will be mentioned where the author finds it to be
appropriate. Do not let this concern deter you from using these solutions in
your own code --- always benchmark to determine where the bottleneck lies in
your software.
\subsection{Extensible Constructors: Revisited}
\label{sec:extending}
Section~\ref{sec:ext-ctor} discussed improving constructor design to allow for
extensibility and to improve performance. However, the solution presented did
not provide a consistent means of creating extensible constructors with, for
example, optional argument lists.
The only way to ensure that the constructor will bypass validation and
initialization logic only when used as a prototype is to somehow indicate that
it is being used as such. Since prototype assignment is in no way different than
any other assignment, no distinction can be made. As such, we must create our
own.
\begin{lstlisting}[%
label=lst:ctor-extend,
caption=Working around prototype extending issues
]
var Foo = ( function( extending )
{
var F = function( name )
{
if ( extending ) return;
if ( typeof name !== 'string' )
{
throw TypeError( "Invalid name" );
}
this.name = name || "Default";
// hypothetical; impl. left to reader
this.hash = createHash();
};
F.asPrototype = function()
{
extending = true;
var proto = new F();
extending = false;
return proto;
};
F.prototype = {
// getName(), etc...
};
return F;
} )( false );
function SubFoo() { /* ... */ }
SubFoo.prototype = Foo.asPrototype(); // OK
// ...
var foo1 = new Foo();
foo1.getName(); // "Default"
foo1.hash; // "..."
var foo2 = new Foo( "Bar" );
foo2.getName(); // "Bar"
foo2.hash; // "..."
\end{lstlisting}
One solution, as demonstrated in \jsref{lst:ctor-extend}, is to use a variable
(e.g. \var{extending}) to indicate to a constructor when it is being used to
extend a prototype. The constructor, acting as a closure, can then check the
value of this flag to determine whether or not to immediately return, avoiding
all construction logic. This implementation would allow us to return only a
prototype, which is precisely what we are looking for.
It is unlikely that we would want to expose \var{extending} directly for
modification, as this would involve manually setting the flag before requesting
the prototype, then remembering to reset it after we are done. Should the user
forget to reset the flag, all future calls to the constructor would continue to
ignore all constructor logic, which could lead to confusing bugs in the
software. To work around this issue, \jsref{lst:ctor-extend} offers an
\func{asPrototype()} method on \var{Foo} itself, which will set the flag, create
a new instance of \var{Foo}, reset the flag and return the new
instance.\footnote{In classical terms, \func{asPrototype()} can be thought of as
a static factory method of \var{Foo}.}
In order to cleanly encapsulate our extension logic, \var{Foo} is generated
within a self-executing function (using much the same concept as privileged
members in section~\ref{sec:privileged}, with a slightly different
application).\footnote{Self-executing functions are most often used to introduce
scope, allowing for the encapsulation of certain data. In this case, we
encapsulate our extension logic and return our constructor (assigned to \var{F}
within the self-executing function), which is then assigned to \var{Foo}. Note
the parenthesis immediately following the anonymous function, which invokes it
with a single argument to give \var{extending} a default value of \code{false}.
This pattern of encapsulation and exporting specific values is commonly referred
to as the \dfn{Module Pattern}.} This gives \var{Foo} complete control over when
its constructor logic should be ignored. Of course, one would not want to
duplicate this mess of code for each and every constructor they create.
Factoring this logic into a common, re-usable implementation will be discussed a
bit later as part of a class system (see section~\ref{sec:ctor-factory}).
\subsection{Encapsulating Data}
\label{sec:encap}
We discussed a basic means of encapsulation with privileged members in
section~\ref{sec:privileged}. Unfortunately, the solution, as demonstrated in
\jsref{lst:privileged}, involves redeclaring methods that could have otherwise
been defined within the prototype and shared between all instances. With that
goal in mind, let us consider how we may be able to share data for multiple
instances with a single method definition in the prototype.
We already know from \jsref{lst:ctor-extend} that we can truly encapsulate data
for a prototype within a self-executing function. Methods can then, acting as
closures, access that data that is otherwise inaccessible to the remainder of
the software. With that example, we concerned ourselves with only a single piece
of data --- the \var{extending} flag. This data has no regard for individual
instances (one could think of it as static data, in classical terms). Using
\jsref{lst:ctor-extend} as a starting point, we can build a system that will
keep track of data \emph{per-instance}. This data will be accessible to all
prototype members.
\subsubsection{A Naive Implementation}
\label{sec:encap-naive}
One approach to our problem involves to assigning each instance a unique
identifier (an ``instance id'', or \var{iid}). For our implementation, this
identifier will simply be defined as an integer that is incremented each time
the constructor is invoked.\footnote{There is, of course, a maximum
number of instances with this implementation. Once \var{iid} reaches
\var{Number.MAX\_NUMBER}, its next assignment will cause it to overflow to
\code{Number.POSITIVE\_INFINITY}. This number, however, can be rather large. On
one 64-bit system under v8, \code{Number.MAX\_NUMBER =
1.7976931348623157e+308.}} This instance id could be used as a key for a data
variable that stores data for each instance. Upon instantiation, the instance
id could be assigned to the new instance as a property (we'll worry about
methods of ``encapsulating'' this property later).
\begin{lstlisting}[%
label=lst:encap-naive,
caption=Encapsulating data with shared members (a naive implementation)
]
var Stack = ( function()
{
var idata = [],
iid = 0;
var S = function()
{
// assign a unique instance identifier
// to each instance
this.__iid = iid++;
idata[ this.__iid ] = {
stack: []
};
};
S.prototype = {
push: function( val )
{
idata[ this.__iid ]
.stack.push( val );
},
pop: function()
{
return idata[ this.__iid ]
.stack.pop();
}
};
return S;
} )();
var first = new Stack(),
second = new Stack();
first.push( "foo" );
second.push( "bar" );
first.pop(); // "foo"
second.pop(); // "bar"
\end{lstlisting}
\jsref{lst:encap-naive} demonstrates a possible stack implementation using the
principals that have just been described. Just like \jsref{lst:ctor-extend}, a
self-executing function is used to encapsulate our data and returns the
\var{Stack} constructor.\footnote{The reader should take note that we have
omitted our extensible constructor solution discussed in
section~\ref{sec:extending} for the sake of brevity.} In addition to the
instance id, the instance data is stored in the array \var{idata} (an array is
appropriate here since \var{iid} is sequential and numeric). \var{idata} will
store an object for each instance, each acting in place of \keyword{this}. Upon
instantiation, the private properties for the new instance are initialized using
the newly assigned instance id.
Because \var{idata} is not encapsulated within the constructor, we do not need
to use the concept of privileged members (see section~\ref{sec:privileged}); we
need only define the methods in such a way that \var{idata} is still within
scope. Fortunately, this allows us to define the methods on the prototype,
saving us method redeclarations with each call to the constructor, improving
overall performance.
This implementation comes at the expense of brevity and creates a diversion from
common ECMAScript convention when accessing data for a particular instance using
prototypes. Rather than having ECMAScript handle this lookup process for us, we
must do so manually. The only data stored on the instance itself (bound to
\keyword{this}) is the instance id, \var{iid}, which is used to look up the
actual members from \var{idata}. Indeed, this is the first concern --- this is a
considerable amount of boilerplate code to create separately for each prototype
wishing to encapsulate data in this manner.
An astute reader may raise concern over our \var{\_\_iid} assignment on each
instance. Firstly, although this name clearly states ``do not touch'' with its
double-underscore prefix,\footnote{Certain languages used double-underscore to
indicate something internal to the language or system. This also ensures the
name will not conflict with any private members that use the single-underscore
prefix convention.} the member is still public and enumerable.\footnote{The term
\dfn{enumerable} simply means that it can be returned by \keyword{foreach}.}
There is no reason why we should be advertising this internal data to the world.
Secondly, imagine what may happen if a user decides to alter the value of
\var{\_\_iid} for a given instance. Although such a modification would create
some fascinating (or even ``cool'') features, it could also wreak havoc on a
system and break encapsulation.\footnote{Consider that we know a stack is
encapsulated within another object. We could exploit this \var{\_\_iid}
vulnerability to gain access to the data of that encapsulated object as follows,
guessing or otherwise calculating the proper instance id: \code{( new Stack()
).\_\_iid = iid\_of\_encapsulated\_stack\_instance}.}
In environments supporting ECMAScript 5 and later, we can make the property
non-enumerable and read-only using \code{Object.defineProperty()} in place of
the \var{\_\_iid} assignment:
\begin{verbatim}
Object.defineProperty( this, '__iid', {
value: iid++,
writable: false,
enumerable: false,
configurable: false
} );
\end{verbatim}
The \var{configurable} property simply determines whether or not we can
re-configure a property in the future using \code{Object.defineProperty()}. It
should also be noted that each of the properties, with the exception of
\var{value}, default to \code{false}, so they may be omitted; they were included
here for clarity.
Of course, this solution leaves a couple loose ends: it will work only on
ECMAScript 5 and later environments (that have support for
\code{Object.defineProperty()}) and it still does not prevent someone from
spying on the instance id should they know the name of the property
(\var{\_\_iid}) ahead of time. However, we do need the instance id to be a
member of the instance itself for our lookup process to work properly.
At this point, many developers would draw the line and call the solution
satisfactory. An internal id, although unencapsulated, provides little room for
exploitation.\footnote{You could get a total count of the number of instances of
a particular prototype, but not much else.} For the sake of discussion and the
development of a more concrete implementation, let us consider a potential
workaround for this issue.
For pre-ES5\footnote{Hereinafter, ECMAScript 5 and ES5 will be used
interchangably.} environments, there will be no concrete solution, since all
properties will always be enumerable. However, we can make it more difficult by
randomizing the name of the \var{\_\_iid} property, which would require that the
user filter out all known properties or guess at the name. In ES5+ environments,
this would effectively eliminate the problem entirely,\footnote{Of course,
debuggers are always an option. There is also the possibility of exploiting
weaknesses in a random name implementation; one can never completely eliminate
the issue.} since the property name cannot be discovered or be known beforehand.
Consider, then, the addition of another variable within the self-executing
function --- \var{iid\_name} --- which we could set to some random value (the
implementation of which we will leave to the reader). Then, when initializing or
accessing values, one would use the syntax:
\begin{verbatim}
idata[ this[ iid_name ] ].stack // ...
\end{verbatim}
Of course, this introduces additional overhead, although it is likely to be
negligible in comparison with the rest of the software.
With that, we have contrived a solution to our encapsulation problem.
Unfortunately, as the title of this section indicates, this implementation is
naive to a very important consideration --- memory consumption. The problem is
indeed so severe that this solution cannot possibly be recommended in practice,
although the core concepts have been an excellent experiment in ingenuity and
have provided a strong foundation on which to expand.\footnote{It is my hope
that the title of this section will help to encourage those readers that simply
skim for code to continue reading and consider the flaws of the design rather
than adopting them.}
\subsubsection{A Proper Implementation}
\label{sec:encap-proper}
Section~\ref{sec:encap-naive} proposed an implementation that would permit the
true encapsulation of instance data, addressing the performance issues
demonstrated in \jsref{lst:privileged}. Unfortunately, the solution offered in
\jsref{lst:encap-naive} is prone to terrible memory leaks. In order to
understand why, we must first understand, on a very basic level, how garbage
collection (GC) is commonly implemented in environments that support ECMAScript.
\dfn{Garbage collection} refers to an automatic cleaning of data (and
subsequent freeing of memory, details of which vary between implementations)
that is no longer ``used''. Rather than languages like C that require manual
allocation and freeing of memory, the various engines that implement ECMAScript
handle this process for us, allowing the developer to focus on the task at hand
rather than developing a memory management system for each piece of software.
Garbage collection can be a wonderful thing in most circumstances, but one must
understand how it recognizes data that is no longer being ``used'' in order to
ensure that the memory is properly freed. If data lingers in memory in such a
way that the software will not access it again and that the garbage collector is
not aware that the data can be freed, this is referred to as a \dfn{memory
leak}.\footnote{The term ``memory leak'' implies different details depending on
context --- in this case, it varies between languages. A memory leak in C is
handled much differently than a memory leak in ECMAScript environments. Indeed,
memory leaks in systems with garbage collectors could also be caused by bugs in
the GC process itself, although this is not the case here.}
One method employed by garbage collectors is reference counting; when an object
is initially created, the reference count is set to one. When a reference to
that object is stored in another variable, that count is incremented by one.
When a variable containing a reference to a particular object falls out of
scope, is deleted, or has the value reassigned, the reference count is
decremented by one. Once the reference count reaches zero, it is scheduled for
garbage collection.\footnote{What happens after this point is
implementation-defined.} The concept is simple, but is complicated by the use of
closures. When an object is referenced within a closure, or even has the
\emph{potential} to be referenced through another object, it cannot be garbage
collected.
In the case of \jsref{lst:encap-naive}, consider \var{idata}. With each new
instance, \var{iid} is incremented and an associated entry added to \var{idata}.
The problem is --- ECMAScript does not have destructor support. Since we cannot
tell when our object is GC'd, we cannot free the \var{idata} entry. Because each
and every object within \var{idata} has the \emph{potential} to be referenced at
some point in the future, even though our implementation does not allow for it,
it cannot be garbage collected. The reference count for each index of
\var{idata} will forever be $\geq 1$.
To resolve this issue without altering this implementation, there is only one
solution --- to offer a method to call to manually mark the object as destroyed.
This defeats the purpose of garbage collection and is an unacceptable solution.
Therefore, our naive implementation contains a fatal design flaw. This extends
naturally into another question --- how do we work with garbage collection to
automatically free the data for us?
The answer to this question is already known from nearly all of our prior
prototype examples. Unfortunately, it is an answer that we have been attempting
to work around in order to enforce encapsulation --- storing the data on the
instance itself. By doing so, the data is automatically freed (if the reference
count is zero, of course) when the instance itself is freed. Indeed, we have hit
a wall due to our inability to explicitly tell the garbage collector when the
data should be freed.\footnote{There may be an implementation out there
somewhere that does allow this, or a library that can interface with the garbage
collector. However, it would not be portable.} The solution is to find a common
ground between \jsref{lst:privileged} and \jsref{lst:encap-naive}.
Recall our original goal --- to shy away from the negative performance impact of
privileged members without exposing each of our private members as public. Our
discussion has already revealed that we are forced to store our data on the
instance itself to ensure that it will be properly freed by the garbage
collector once the reference count reaches zero. Recall that
section~\ref{sec:encap-naive} provided us with a number of means of making our
only public member, \var{\_\_iid}, considerably more difficult to access, even
though it was not fully encapsulated. This same concept can be applied to our
instance data.
\begin{lstlisting}[%
label=lst:encap-inst,
caption=Encapsulating data on the instance itself (see also
\jsref{lst:encap-naive})
]
var Stack = ( function()
{
// implementation left to reader
var _privname = genRandomName();
var S = function()
{
Object.defineProperty( this, _privname, {
enumerable: false,
writable: false,
configurable: false,
value: {
stack: []
}
} );
};
S.prototype = {
push: function( val )
{
this[ _privname ].stack.push( val );
},
pop: function()
{
return this[ _privname ].stack.pop();
}
};
return S;
} )();
\end{lstlisting}
\jsref{lst:encap-inst} uses a random, non-enumerable property to make the
discovery of the private data considerably more difficult.\footnote{The property
is also read-only, but that does not necessarily aid encapsulation. It prevents
the object itself from being reassigned, but not any of its members.} The random
name, \var{\_privname}, is used in each of the prototypes to look up the data on
the appropriate instance (e.g. \code{this[ \_privname ].stack} in place of
\code{this.stack}).\footnote{One may decide that the random name is unnecessary
overhead. However, note that static names would permit looking up the data if
the name is known ahead of time.} This has the same effect as
\jsref{lst:encap-naive}, with the exception that it is a bit easier to follow
without the instance management code and that it does not suffer from memory
leaks due to GC issues.
Of course, this implementation depends on features introduced in ECMAScript 5
--- namely, \code{Object.defineProperty()}, as introduced in
section~\ref{sec:encap-naive}. In order to support pre-ES5 environments, we
could define our own fallback \func{defineProperty()} method by directly
altering \var{Object},\footnote{The only circumstance I ever recommend modifying