Initial commit of working draft, revised
commit
74476ca64a
|
@ -0,0 +1,7 @@
|
|||
*.aux
|
||||
*.log
|
||||
*.toc
|
||||
*.dvi
|
||||
*.pdf
|
||||
*.ps
|
||||
*.out
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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.}
|
|
@ -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}
|
||||
|
|
@ -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}
|
|
@ -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.
|
|
@ -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}
|
|
@ -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
|
||||
built-in bojects/prototypes is to aid in backward compatibility; it is otherwise
|
||||
a very poor practice that creates tightly coupled, unportable code.} as
|
||||
demonstrated in \jsref{lst:defprop}.
|
||||
|
||||
\begin{lstlisting}[%
|
||||
label=lst:defprop,
|
||||
caption=A fallback \code{Object.defineProperty()} implementation
|
||||
]
|
||||
Object.defineProperty = Object.defineProperty
|
||||
|| function( obj, name, config )
|
||||
{
|
||||
obj[ name ] = config.value;
|
||||
};
|
||||
\end{lstlisting}
|
||||
|
||||
Unfortunately, a fallback implementation is not quite so simple. Certain
|
||||
dialects may only partially implement \code{Object.createProperty()}. In
|
||||
particular, I am referring to Internet Explorer 8's incomplete
|
||||
implementation.\footnote{IE8's dialect is JScript.} Surprisingly, IE8 only
|
||||
supports this action on DOM elements, not all objects. This puts us in a
|
||||
terribly awkward situation --- the method is defined, but the implementation is
|
||||
``broken''. As such, our simple and fairly concise solution in
|
||||
\jsref{lst:defprop} is insufficient. Instead, we need to perform a more
|
||||
complicated check to ensure that not only is the method defined, but also
|
||||
functional for our particular uses. This check is demonstrated in
|
||||
\jsref{lst:defprop-check}, resulting in a boolean value which can be used to
|
||||
determine whether or not the fallback in \jsref{lst:defprop} is necessary.
|
||||
|
||||
\begin{lstlisting}[%
|
||||
label=lst:defprop-check,
|
||||
caption=Working around IE8's incomplete \code{Object.defineProperty()}
|
||||
implementation (taken from ease.js)
|
||||
]
|
||||
var can_define_prop = ( function()
|
||||
{
|
||||
try
|
||||
{
|
||||
Object.defineProperty( {}, 'x', {} );
|
||||
}
|
||||
catch ( e ) { return false; }
|
||||
|
||||
return true;
|
||||
} )();
|
||||
\end{lstlisting}
|
||||
|
||||
This function performs two checks simultaneously --- it first checks to see if
|
||||
\code{Object.defineProperty()} exists and then ensures that we are not using
|
||||
IE8's broken implementation. If the invocation fails, that will mean that the
|
||||
method does not exist (or is not properly defined), throwing an exception which
|
||||
will immediately return false. If attempting to define a property using this
|
||||
method on a non-DOM object in IE8, an exception will also be thrown, returning
|
||||
false. Therefore, we can simply attempt to define a property on an empty object.
|
||||
If this action succeeds, then \code{Object.defineProperty()} is assumed to be
|
||||
sufficiently supported. The entire process is enclosed in a self-executing
|
||||
function to ensure that the check is performed only once, rather than a function
|
||||
that performs the check each time it is called. The merriment of this result to
|
||||
\jsref{lst:defprop} is trivial and is left to the reader.
|
||||
|
||||
It is clear from this fallback, however, that our property is enumerable in
|
||||
pre-ES5 environments. At this point, a random property name would not be all
|
||||
that helpful and the reader may decide to avoid the random implementation in its
|
||||
entirety.
|
||||
|
||||
\subsubsection{Private Methods}
|
||||
\label{sec:priv-methods}
|
||||
Thus far, we have been dealing almost exclusively with the issue of
|
||||
encapsulating properties. Let us now shift our focus to the encapsulation of
|
||||
other private members, namely methods (although this could just as easily be
|
||||
applied to getters/setters in ES5+ environments). Private methods are actually
|
||||
considerably easier to conceptualize, because the data does not vary between
|
||||
instances --- a method is a method and is shared between all instances. As such,
|
||||
we do not have to worry about the memory management issues addressed in
|
||||
section~\ref{sec:encap-proper}.
|
||||
|
||||
Encapsulating private members would simply imply moving the members outside of
|
||||
the public prototype (that is, \code{Stack.prototype}). One would conventionally
|
||||
implement private methods using privileged members (as in
|
||||
section~\ref{sec:privileged}), but it is certainly pointless redefining the
|
||||
methods for each instance, since \jsref{lst:encap-inst} provided us with a means
|
||||
of accessing private data from within the public prototype. Since the
|
||||
self-executing function introduces scope for our private data (instead of the
|
||||
constructor), we do not need to redefine the methods for each new instance.
|
||||
Instead, we can create what can be considered a second, private prototype.
|
||||
|
||||
\begin{lstlisting}[%
|
||||
label=lst:method-priv,
|
||||
caption=Implementing shared private methods without privileged members
|
||||
]
|
||||
var Stack = ( function()
|
||||
{
|
||||
var _privname = getRandomName();
|
||||
|
||||
var S = function()
|
||||
{
|
||||
// ... (see previous examples)
|
||||
};
|
||||
|
||||
var priv_methods = {
|
||||
getStack: function()
|
||||
{
|
||||
return this[ _privname ].stack;
|
||||
}
|
||||
};
|
||||
|
||||
S.prototype = {
|
||||
push: function( val )
|
||||
{
|
||||
var stack = priv_methods.getStack
|
||||
.call( this );
|
||||
stack.push( val );
|
||||
},
|
||||
|
||||
pop: function()
|
||||
{
|
||||
var stack = priv_methods.getStack
|
||||
.call( this )
|
||||
return stack.pop( val );
|
||||
}
|
||||
};
|
||||
|
||||
return S;
|
||||
} )();
|
||||
\end{lstlisting}
|
||||
|
||||
\jsref{lst:method-priv} illustrates this concept of a private
|
||||
prototype.\footnote{Alternatively, to reduce code at the expense of clarity, one
|
||||
could simply define functions within the closure to act as private methods
|
||||
without assigning them to \var{priv\_methods}. Note that \func{call()} is still
|
||||
necessary in that situation.} The object \var{priv\_methods} acts as a second
|
||||
prototype containing all members that are private and shared between all
|
||||
instances, much like the conventional prototype. \code{Stack.prototype} then
|
||||
includes only the members that are intended to be public. In this case, we have
|
||||
defined a single private method --- \func{getStack()}.
|
||||
|
||||
Recall how \keyword{this} is bound automatically for prototype methods (see
|
||||
section~\ref{sec:proto}). ECMAScript is able to do this for us because of the
|
||||
standardized \var{prototype} property. For our private methods, we have no such
|
||||
luxury. Therefore, we are required to bind \keyword{this} to the proper object
|
||||
ourselves through the use of \code{Function.call()} (or, alternatively,
|
||||
\code{Function.apply()}). The first argument passed to \func{call()} is the
|
||||
object to which \keyword{this} will be bound, which we will refer to as the
|
||||
\dfn{context}. This, unfortunately, increases the verbosity of private method
|
||||
calls, but successfully provides us with a private prototype implementation.
|
||||
|
||||
Since private members needn't be inherited by subtypes, no additional work needs
|
||||
to be done.
|
||||
|
||||
\subsection{Protected Members}
|
||||
We have thus far covered two of the three access modifiers (see
|
||||
section~\ref{sec:encap}) --- public and private. Those implementations allowed
|
||||
us to remain blissfully ignorant of inheritance, as public members are handled
|
||||
automatically by ECMAScript and private members are never inherited by subtypes.
|
||||
The concept of protected members is a bit more of an interesting study since it
|
||||
requires that we put thought into providing subtypes with access to encapsulated
|
||||
data, \emph{without} exposing this data to the rest of the world.
|
||||
|
||||
From an implementation perspective, we can think of protected members much like
|
||||
private; they cannot be part of the public prototype, so they must exist in
|
||||
their own protected prototype and protected instance object. The only difference
|
||||
here is that we need a way to expose this data to subtypes. This is an issue
|
||||
complicated by our random name implementation (see
|
||||
section~\ref{sec:encap-proper}); without it, subtypes would be able to access
|
||||
protected members of its parent simply by accessing a standardized property
|
||||
name. The problem with that is --- if subtypes can do it, so can other,
|
||||
completely unrelated objects. As such, we will focus on a solution that works in
|
||||
conjunction with our randomized name (an implementation with a standardized
|
||||
name is trivial).
|
||||
|
||||
In order for the data to remain encapsulated, the name must too remain
|
||||
encapsulated. This means that the subtype cannot request the name from the
|
||||
parent; instead, we must either have access to the random name or we must
|
||||
\emph{tell} the parent what the name should be. The latter will not work
|
||||
per-instance with the implementation described in
|
||||
section~\ref{sec:encap-proper}, as the methods are not redefined per-instance
|
||||
and therefore must share a common name. Let us therefore first consider the
|
||||
simpler of options --- sharing a common protected name between the two classes.
|
||||
|
||||
\begin{lstlisting}[%
|
||||
label=lst:prot-share,
|
||||
caption=Sharing protected members with subtypes
|
||||
]
|
||||
var _protname = getRandomName();
|
||||
|
||||
var Stack = ( function()
|
||||
{
|
||||
var _privname = getRandomName();
|
||||
|
||||
var S = function()
|
||||
{
|
||||
// ... (see previous examples)
|
||||
|
||||
Object.defineProperty( this, _privname, {
|
||||
value: { stack: [] }
|
||||
} );
|
||||
|
||||
Object.defineProperty( this, _protname, {
|
||||
value: { empty: false }
|
||||
} );
|
||||
};
|
||||
|
||||
// a means of sharing protected methods
|
||||
Object.defineProperty( S, _protname, {
|
||||
getStack: function()
|
||||
{
|
||||
return this[ _privname ].stack;
|
||||
}
|
||||
} );
|
||||
|
||||
S.prototype = {
|
||||
push: function( val )
|
||||
{
|
||||
var stack = S[ _protname ].getStack
|
||||
.call( this );
|
||||
stack.push( val );
|
||||
|
||||
this[ _protname ].empty = false;
|
||||
},
|
||||
|
||||
pop: function()
|
||||
{
|
||||
var stack = this[ _protname ]
|
||||
.getStack.call( this )
|
||||
|
||||
this[ _protname ].empty =
|
||||
( stack.length === 0 );
|
||||
|
||||
return stack.pop( val );
|
||||
}
|
||||
};
|
||||
|
||||
S.asPrototype = function()
|
||||
{
|
||||
// ... (see previous examples)
|
||||
};
|
||||
|
||||
return S;
|
||||
} )();
|
||||
|
||||
|
||||
var MaxStack = ( function()
|
||||
{
|
||||
var M = function( max )
|
||||
{
|
||||
// call parent constructor
|
||||
Stack.call( this );
|
||||
|
||||
// we could add to our protected members
|
||||
// (in practice, this would be private, not
|
||||
// protected)
|
||||
this[ _protname ].max = +max;
|
||||
};
|
||||
|
||||
// override push
|
||||
M.prototype.push = function( val )
|
||||
{
|
||||
var stack = Stack[ _protname ].getStack
|
||||
.call( this );
|
||||
|
||||
if ( stack.length ===
|
||||
this[ _protname ].max
|
||||
)
|
||||
{
|
||||
throw Error( "Maximum reached." );
|
||||
};
|
||||
|
||||
// call parent method
|
||||
Stack.prototype.push.call( this, val );
|
||||
};
|
||||
|
||||
// add a new method demonstrating parent
|
||||
// protected property access
|
||||
M.prototype.isEmpty = function()
|
||||
{
|
||||
return this[ _protname ].empty;
|
||||
};
|
||||
|
||||
M.prototype = Stack.asPrototype();
|
||||
M.prototype.constructor = M;
|
||||
|
||||
return M;
|
||||
} )();
|
||||
|
||||
|
||||
var max = new MaxStack( 2 );
|
||||
max.push( "foo" );
|
||||
max.push( "bar" );
|
||||
max.push( "baz" ); // Error
|
||||
max.pop(); // "bar"
|
||||
max.pop(); // "foo"
|
||||
\end{lstlisting}
|
||||
|
||||
\jsref{lst:prot-share} makes an attempt to demonstrate a protected property and
|
||||
method implementation while still maintaining the distinction between it and the
|
||||
private member implementation (see section~\ref{sec:encap-proper}). The example
|
||||
contains two separate constructors --- \var{Stack} and \var{MaxStack}, the
|
||||
latter of which extends \var{Stack} to limit the number of items that may be
|
||||
pushed to it. \var{Stack} has been modified to include a protected property
|
||||
\var{empty}, which will be set to \code{true} when the stack contains no items,
|
||||
and a protected method \var{getStack()}, which both \var{Stack} and its subtype
|
||||
\var{MaxStack} may use to access the private property \var{stack} of
|
||||
\var{Stack}.
|
||||
|
||||
The key part of this implementation is the declaration of \var{\_protname}
|
||||
within the scope of both types (\var{Stack} and \var{MaxStack}).\footnote{One
|
||||
would be wise to enclose all of \jsref{lst:prot-share} within a function to
|
||||
prevent \var{\_protname} from being used elsewhere, exporting \var{Stack} and
|
||||
\var{MaxStack} however the reader decides.} This declaration allows both
|
||||
prototypes to access the protected properties just as we would the private
|
||||
data. Note that \var{\_privname} is still defined individually within each type,
|
||||
as this data is unique to each.
|
||||
|
||||
Protected methods, however, need additional consideration. Private methods, when
|
||||
defined within the self-executing function that returns the constructor, work
|
||||
fine when called from within the associated prototype (see
|
||||
section~\ref{sec:priv-methods}). However, since they're completely encapsulated,
|
||||
we cannot use the same concept for protected methods --- the subtype would not
|
||||
have access to the methods. Our two options are to either declare the protected
|
||||
members outside of the self-executing function (as we do \var{\_privname}), which
|
||||
makes little organizational sense, or to define the protected members on the
|
||||
constructor itself using \var{\_protname} and
|
||||
\code{Object.defineProperty()}\footnote{See section~\ref{sec:encap-proper} for
|
||||
\code{Object.defineProperty()} workarounds/considerations.} to encapsulate it
|
||||
the best we can. We can then use the shared \var{\_protname} to access the
|
||||
methods on \var{Stack}, unknown to the rest of the world.
|
||||
|
||||
An astute reader may realize that \jsref{lst:prot-share} does not permit the
|
||||
addition of protected methods without also modifying the protected methods of
|
||||
the supertype and all other subtypes; this is the same reason we assign new
|
||||
instances of constructors to the \var{prototype} property. Additionally,
|
||||
accessing a protected method further requires referencing the same constructor
|
||||
on which it was defined. Fixing this implementation is left as an exercise to
|
||||
the reader.
|
||||
|
||||
Of course, there is another glaring problem with this implementation --- what
|
||||
happens if we wish to extend one of our prototypes, but are not within the scope
|
||||
of \var{\_protname} (which would be the case if you are using
|
||||
\jsref{lst:prot-share} as a library, for example)? With this implementation,
|
||||
that is not possible. As such, \jsref{lst:prot-share} is not recommended unless
|
||||
you intended to have your prototypes act like final classes.\footnote{A
|
||||
\dfn{final} class cannot be extended.} As this will not always be the case, we
|
||||
must put additional thought into the development of a solution that allows
|
||||
extending class-like objects with protected members outside of the scope of the
|
||||
protected name \var{\_protname}.
|
||||
|
||||
As we already discussed, we cannot request the protected member name from the
|
||||
parent, as that will provide a means to exploit the implementation and gain
|
||||
access to the protected members, thereby breaking encapsulation. Another
|
||||
aforementioned option was \emph{telling} the parent what protected member name
|
||||
to use, perhaps through the use of \func{asPrototype()} (see
|
||||
section~\ref{sec:extending}). This is an option for protected \emph{properties},
|
||||
as they are initialized with each new instance, however it is not a clean
|
||||
implementation for \emph{members}, as they have already been defined on the
|
||||
constructor with the existing \var{\_protname}. Passing an alternative name
|
||||
would result in something akin to:
|
||||
|
||||
\begin{verbatim}
|
||||
Object.defineProperty( S, _newname, {
|
||||
value: S[ _protname ]
|
||||
} );
|
||||
\end{verbatim}
|
||||
|
||||
This would quickly accumulate many separate protected member references on the
|
||||
constructor --- one for each subtype. As such, this implementation is also left
|
||||
as an exercise for an interested reader; we will not explore it
|
||||
further.\footnote{The reader is encouraged to attempt this implementation to
|
||||
gain a better understanding of the concept. However, the author cannot recommend
|
||||
its use in a production environment.}
|
||||
|
||||
The second option is to avoid exposing protected property names entirely. This
|
||||
can be done by defining a function that can expose the protected method object.
|
||||
This method would use a system-wide protected member name to determine what
|
||||
objects to return, but would never expose the name --- only the object
|
||||
references. However, this does little to help us with our protected properties,
|
||||
as a reference to that object cannot be returned until instantiation. As such,
|
||||
one could use a partial implementation of the previously suggested
|
||||
implementation in which one provides the protected member name to the parent(s).
|
||||
Since the protected members would be returned, the duplicate reference issue
|
||||
will be averted.
|
||||
|
||||
The simplest means of demonstrating this concept is to define a function that
|
||||
accepts a callback to be invoked with the protected method object. A more
|
||||
elegant implementation will be described in future sections, so a full
|
||||
implementation is also left as an exercise to the reader. \jsref{lst:prot-func}
|
||||
illustrates a skeleton implementation.\footnote{Should the reader decide to take
|
||||
up this exercise, keep in mind that the implementation should also work with
|
||||
multiple supertypes (that is, type3 extends type2 extends type1).} The
|
||||
\func{def} function accepts the aforementioned callback with an optional first
|
||||
argument --- \var{base} --- from which to retrieve the protected methods.
|
||||
|
||||
\begin{lstlisting}[%
|
||||
label=lst:prot-func,
|
||||
caption=Exposing protected methods with a callback (brief illustration; full
|
||||
implementation left as an exercise for the reader)
|
||||
]
|
||||
var def = ( function()
|
||||
{
|
||||
var _protname = getRandomName();
|
||||
|
||||
return function( base, callback )
|
||||
{
|
||||
var args = Array.prototype.slice.call(
|
||||
arguments
|
||||
),
|
||||
|
||||
callback = args.pop(),
|
||||
base = args.pop() || {};
|
||||
|
||||
return callback( base[ _protname ] );
|
||||
};
|
||||
} )();
|
||||
|
||||
var Stack = def( function( protm )
|
||||
{
|
||||
// ...
|
||||
|
||||
return S;
|
||||
} );
|
||||
|
||||
var MaxStack = def( Stack, function( protm )
|
||||
{
|
||||
// for properties only
|
||||
var _protname = getRandomName();
|
||||
|
||||
// ...
|
||||
|
||||
// asPrototype() would accept the protected
|
||||
// member name
|
||||
M.protoype = S.asPrototype( _protname );
|
||||
M.prototype.constructor = M;
|
||||
|
||||
return M;
|
||||
} );
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Protected Member Encapsulation Challenges}
|
||||
Unfortunately, the aforementioned implementations do not change a simple fact
|
||||
--- protected members are open to exploitation, unless the prototype containing
|
||||
them cannot be extended outside of the library/implementation. Specifically,
|
||||
there is nothing to prevent a user from extending the prototype and defining a
|
||||
property or method to return the encapsulated members.
|
||||
|
||||
Consider the implementation described in \jsref{lst:prot-func}. We could define
|
||||
another subtype, \var{ExploitedStack}, as shown in \jsref{lst:prot-exploit}.
|
||||
This malicious type exploits our implementation by defining two methods ---
|
||||
\func{getProtectedProps()} and \func{getProtectedMethods()} --- that return
|
||||
the otherwise encapsulated data.
|
||||
|
||||
\begin{lstlisting}%
|
||||
[label=lst:prot-exploit,
|
||||
caption=Exploiting \jsref{lst:prot-func} by returning protected members.
|
||||
]
|
||||
var ExploitedStack = def( Stack, function( protm )
|
||||
{
|
||||
var _protname = getRandomName();
|
||||
|
||||
var E = function() { /* ... */ };
|
||||
|
||||
E.prototype.getProtectedProps = function()
|
||||
{
|
||||
return this[ _protname ];
|
||||
}:
|
||||
|
||||
E.prototype.getProtectedMethods = function()
|
||||
{
|
||||
return protm;
|
||||
};
|
||||
|
||||
E.prototype = Stack.asPrototype( _protname );
|
||||
E.prototype.constructor = E;
|
||||
|
||||
return E;
|
||||
} )();
|
||||
\end{lstlisting}
|
||||
|
||||
Fortunately, our random \var{\_protname} implementation will only permit
|
||||
returning data for the protected members of that particular instance. Had we not
|
||||
used random names, there is a chance that an object could be passed to
|
||||
\func{getProtectedProps()} and have its protected properties
|
||||
returned.\footnote{Details depend on implementation. If a global protected
|
||||
property name is used, this is trivial. Otherwise, it could be circumstantial
|
||||
--- a matching name would have to be guessed, known, or happen by chance.} As
|
||||
such, this property exploit is minimal and would only hurt that particular
|
||||
instance. There could be an issue if supertypes contain sensitive protected
|
||||
data, but this is an implementation issue (sensitive data should instead be
|
||||
private).
|
||||
|
||||
Methods, however, are a more considerable issue. Since the object exposed via
|
||||
\func{def()} is \emph{shared} between each of the instances, much like its
|
||||
parent prototype is, it can be used to exploit each and every instance (even if
|
||||
the reader has amended \jsref{lst:prot-share} to resolve the aforementioned
|
||||
protected member addition bug, since \code{Object.getPrototypeOf()} can be
|
||||
used to work around this amendment). Someone could, for example, reassign
|
||||
\code{Stack[ \_protname ].getStack()} to do something else;
|
||||
\var{Object.defineProperty()} in \jsref{lst:prot-share} only made \code{Stack[
|
||||
\_protname ]} \emph{itself} read-only. The object itself, however, can be
|
||||
modified. This can be amended by using \code{Object.defineProperty()} for each
|
||||
and every protected method, which is highly verbose and cumbersome.
|
||||
|
||||
Once we rule out the ability to modify protected method definitions,\footnote{Of
|
||||
course, this doesn't secure the members in pre-ES5 environments.} we still must
|
||||
deal with the issue of having our protected methods exposed and callable. For
|
||||
example, one could do the following to gain access to the private \var{stack}
|
||||
object:
|
||||
|
||||
\begin{verbatim}
|
||||
( new ExploitedStack() ).getProtectedMethods()
|
||||
.getStack.call( some_stack_instance );
|
||||
\end{verbatim}
|
||||
|
||||
Unfortunately, there is little we can do about this type of exploit besides
|
||||
either binding\footnote{See \func{Function.bind()}.} each method call (which
|
||||
would introduce additional overhead per instance) or entirely preventing the
|
||||
extension of our prototypes outside of our own library/software. By creating a
|
||||
protected API, you are exposing certain aspects of your prototype to the rest of
|
||||
the world; this likely breaks encapsulation and, in itself, is often considered
|
||||
a poor practice.\footnote{\code{Stack.getStack()} breaks encapsulation because
|
||||
it exposes the private member \var{stack} to subtypes.} An alternative is to
|
||||
avoid inheritance altogether and instead favor composition, thereby evading this
|
||||
issue entirely. That is a pretty attractive concept, considering how verbose and
|
||||
messy this protected hack has been.
|
|
@ -0,0 +1,48 @@
|
|||
\section{Licenses}
|
||||
This document and all source code contained within is free,\footnote{Free as in
|
||||
``free speech'', not ``free beer''.} released under the GNU FDL. The source code
|
||||
contained within the listings are also licensed under the GNU GPL, unless
|
||||
otherwise noted within this section, to permit their use in free software. The
|
||||
code listings are intended primarily for instruction and example and may not be
|
||||
directly applicable to your software. If licensing is a concern, one may use
|
||||
the listings to implement the code from scratch rather than using the code
|
||||
verbatim.
|
||||
|
||||
Each license may be found at http://www.gnu.org/licenses/.
|
||||
|
||||
\subsection{Document License}
|
||||
\begin{quote}
|
||||
\small\texttt
|
||||
Copyright \copyright{} 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".
|
||||
\end{quote}
|
||||
|
||||
\subsection{Code Listing License}
|
||||
\begin{quote}
|
||||
\small\texttt
|
||||
Copyright \copyright{} 2012 Mike Gerwitz.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
\end{quote}
|
||||
|
||||
\subsubsection{Code Listing License Exceptions}
|
||||
The following listings are provided under alternative licenses.
|
||||
|
||||
\begin{description}
|
||||
\small
|
||||
\item[\jsref{lst:defprop-check}] GNU LGPL (taken from ease.js)
|
||||
\end{description}
|
Loading…
Reference in New Issue