From 74476ca64a8c14cb347a37be07be13a86fce141b Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 15 Feb 2012 23:11:23 -0500 Subject: [PATCH] Initial commit of working draft, revised --- .gitignore | 7 + COPYING | 451 ++++++++++++++++++++ Makefile | 15 + README | 15 + abstract.tex | 22 + coope.sty | 82 ++++ coope.tex | 44 ++ sec/class-like.tex | 543 ++++++++++++++++++++++++ sec/encap-hacks.tex | 131 ++++++ sec/hacking-proto.tex | 956 ++++++++++++++++++++++++++++++++++++++++++ sec/licenses.tex | 48 +++ 11 files changed, 2314 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 abstract.tex create mode 100644 coope.sty create mode 100644 coope.tex create mode 100644 sec/class-like.tex create mode 100644 sec/encap-hacks.tex create mode 100644 sec/hacking-proto.tex create mode 100644 sec/licenses.tex diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfef807 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.aux +*.log +*.toc +*.dvi +*.pdf +*.ps +*.out diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..2f7e03c --- /dev/null +++ b/COPYING @@ -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. + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..057268a --- /dev/null +++ b/Makefile @@ -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 diff --git a/README b/README new file mode 100644 index 0000000..52ab66b --- /dev/null +++ b/README @@ -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. diff --git a/abstract.tex b/abstract.tex new file mode 100644 index 0000000..1d1d85d --- /dev/null +++ b/abstract.tex @@ -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.} diff --git a/coope.sty b/coope.sty new file mode 100644 index 0000000..67cd6d7 --- /dev/null +++ b/coope.sty @@ -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} + diff --git a/coope.tex b/coope.tex new file mode 100644 index 0000000..7642bff --- /dev/null +++ b/coope.tex @@ -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} diff --git a/sec/class-like.tex b/sec/class-like.tex new file mode 100644 index 0000000..efeec7b --- /dev/null +++ b/sec/class-like.tex @@ -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. diff --git a/sec/encap-hacks.tex b/sec/encap-hacks.tex new file mode 100644 index 0000000..55c3b3c --- /dev/null +++ b/sec/encap-hacks.tex @@ -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} diff --git a/sec/hacking-proto.tex b/sec/hacking-proto.tex new file mode 100644 index 0000000..683cad9 --- /dev/null +++ b/sec/hacking-proto.tex @@ -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. diff --git a/sec/licenses.tex b/sec/licenses.tex new file mode 100644 index 0000000..84b224d --- /dev/null +++ b/sec/licenses.tex @@ -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}