-
Notifications
You must be signed in to change notification settings - Fork 32
/
legacy.tex
210 lines (172 loc) · 6.59 KB
/
legacy.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
\chapter{Legacy JavaScript Issues}\label{s:legacy}
JavaScript is now twenty-five years old,
and like many twenty-somethings,
it is still struggling with issues from its childhood.
This appendix explores three of them.
\section{Equality}\label{s:legacy-equality}
Gary Bernhardt's \hreffoot{https://www.destroyallsoftware.com/talks/wat}{lightning talk from 2012}
may be the most-watched presentation on JavaScript ever.
In it,
he rattles through some truths about the language that may surprise you
(\tblref{t:legacy-surprises}).
\begin{longtable}{lll}
Operation & Code & Result \\
empty array plus empty array & \texttt{[]\ +\ []} & \texttt{""} (empty string) \\
empty array plus empty object & \texttt{[]\ +\ \{\}} & \texttt{\{\}} (empty object) \\
empty object plus empty array & \texttt{\{\}\ +\ []} & \texttt{0} (number zero) \\
empty object plus empty object & \texttt{\{\}\ +\ \{\}} & \texttt{NaN} (not a number) \\
\caplbl{Surprising Results}{t:legacy-surprises}
\end{longtable}
In order to understand this, we need to know several things
(which are laid out in more detail in \hreffoot{https://medium.com/dailyjs/the-why-behind-the-wat-an-explanation-of-javascripts-weird-type-system-83b92879a8db}{this article} by Abhinav Suri):
\begin{enumerate}
\item
Arrays are objects whose keys happen to be sequential integers.
\item
When JavaScript tries to add things that aren't numbers,
it tries to convert them to numbers,
and if that doesn't work,
to strings (because it can always concatenate strings).
\item
To convert an array to a string,
JavaScript converts the elements to strings and concatenates them.
If the array is empty, the result is an empty string.
\item
When converting an object to a string,
JavaScript produces \texttt{[object\ CLASS]},
where \texttt{CLASS} is the name of the object's class.
\item
\texttt{\{\}} can be interpreted as either an empty object \emph{or} an empty block of code.
\end{enumerate}
\noindent
So:
\begin{itemize}
\item
Empty array plus empty array becomes empty string plus empty string.
\item
Empty array plus empty object becomes empty string plus \texttt{[object\ Object]}
(because the class of an empty object is just \texttt{Object}).
\item
\texttt{\{\}\ +\ []} is ``an empty block of code producing nothing, followed by \texttt{+[]}'',
which becomes ``+ of the numeric value of the string value of an empty array'',
which becomes ``+ of 0''.
\item
Empty object plus empty object is interpreted as an empty object plus an empty block of code,
and since an empty block of code doesn't produce a result,
its ``value'' is \texttt{NaN} (not a number).
\end{itemize}
This is one of many cases in programming (and real life) where
doing something that's convenient in a simple case
produces confusion in less common cases.
Every language except Canadian English has warts like these.
\section{Iteration}\label{s:legacy-iteration}
We wrote above that arrays are objects.
This led to some undesirable behavior with JavaScript's original \texttt{for} loop,
which used the word \texttt{in} rather than \texttt{of},
and which looped over all of an object's enumerable keys:
\begin{minted}{js}
const things = ['x', 'y', 'z']
things.someProperty = 'someValue'
for (let key in things) {
console.log(key)
}
\end{minted}
\begin{minted}{text}
0
1
2
someProperty
\end{minted}
That phrase ``enumerable keys'' conceals some strangeness of its own,
but in brief,
a \texttt{for-in} loop will loop over keys inherited from the object's parents
as well as those defined in the object itself.
Since this is usually not what programmers want (especially for arrays),
older code often used a C-style loop:
\begin{minted}{js}
for (let i = 0; i < things.length; i += 1) {
console.log(i)
}
\end{minted}
\begin{minted}{text}
0
1
2
\end{minted}
Today's solution is to use \texttt{for-of} to get the \emph{values} from an array,
which is usually what we want:
\begin{minted}{js}
for (let key of things) {
console.log(key)
}
\end{minted}
\begin{minted}{text}
x
y
z
\end{minted}
\noindent
Better yet, use \texttt{forEach} and take advantage of its optional second and third arguments:
\begin{minted}{js}
things.forEach((val, loc, array) => {
console.log(`element ${loc} of ${array} is ${val}`)
})
\end{minted}
\begin{minted}{text}
element 0 of x,y,z is x
element 1 of x,y,z is y
element 2 of x,y,z is z
\end{minted}
\section{Prototypes}\label{s:legacy-prototypes}
We come finally to an aspect of JavaScript that has been the cause of a great deal of confusion: prototypes.
Every JavaScript object has an internal property called its \gref{g:prototype}{prototype}.
If you try to access some property of an object and it's not found,
JavaScript automatically looks in the object that the first object's prototype refers to.
If the desired property isn't there,
JavaScript looks in the prototype object's prototype, and so on.
So where do prototypes come from?
If an object is created with \texttt{new\ Something()},
and the function \texttt{Something} has a property called \texttt{prototype},
then the new object's prototype is set to the object to which that
\texttt{prototype} property points.
This will all make sense with an example and a diagram.
Let's create an object to store the default properties of ice cream cones,
then create a function \texttt{Cone} that creates an actual cone:
\begin{minted}{js}
const iceCream = {
size: 'large'
}
const Cone = function(f) {
this.flavor = f
}
Cone.prototype = iceCream
\end{minted}
We can now create a cone and look at its properties
(\figref{f:legacy-prototype}):
\begin{minted}{js}
const dessert = new Cone('mustard')
console.log(`flavor "${dessert.flavor}" size "${dessert.size}"`)
\end{minted}
\begin{minted}{text}
flavor "mustard" size "large"
\end{minted}
\figpdf{figures/legacy-prototype.pdf}{Prototypes}{f:legacy-prototype}
If we change the \texttt{size} of our dessert,
lookup finds the object's property before looking up the chain to find the parent object's:
\begin{minted}{js}
dessert.size = 'extra-large'
console.log(`new flavor "${dessert.flavor}" size "${dessert.size}"`)
\end{minted}
\begin{minted}{text}
new flavor "mustard" size "extra-large"
\end{minted}
Prototypes are a way to implement inheritance for object-oriented programming;
the problem is that the mechanics are rather clumsy,
and very different from what most programmers are used to,
so people built a variety of layers on top of prototypes.
To make things even more confusing,
\texttt{this} can behave in some rather odd ways,
and again,
people built layers to try to insulate themselves from those oddities.
Prototypes still have their fans,
but most people find modern JavaScript's classes easier to use.