All of the projects in this class will be in JavaScript, and we expect you to pick up the language as we go along. This is a short introduction to get you started. For a more in-depth introduction to JavaScript, we recommend the first 100 pages or so of JavaScript: The Good Parts, by Doug Crockford.
Note: There are several versions of JavaScript out there. The particular version we’re talking about here is ECMAScript 6 in strict mode, which is a restricted variant of the language with somewhat saner semantics.
Who in their right mind would prototype a programming language in JavaScript?!? It’s really not as crazy as it sounds. For starters, JavaScript is a powerful and flexible dynamic language with decent support for OO and functional programming. Granted, there are better programming languages out there that you could use. But what makes JavaScript an interesting option is the fact that it is the language of the web browser… and everyone has a web browser.
When you prototype a programming language in the web browser:
Alright, that’s enough motivation. Let’s dive in.
JavaScript’s objects are dictionaries that map property names to values. You can
create an object by calling a constructor, e.g.,
To access and assign values to the properties of an object, you use the
.
operator, e.g.,
'white'
.7
.toString()
method. In the admittedly nonsensical example above,
someObject
’s
toString()
returns '[object Object]'
toString()
is pretty useless.daddysShirt
does not have a property with that name,
daddysShirt[someObject]
will evaluate to the special value
undefined
.
Last but not least, you can remove a property using the delete
statement. For example, afterdaddysShirt.color
will evaluate to undefined
.
JavaScript is a prototype-based language: every object has a prototype
from which it may inherit properties. For example, the prototype of all strings is
String.prototype
, which is where methods like indexOf
and
substr
are defined. (More on these methods, and how methods work, later.)
p
in
obj
and obj
doesn’t have it, the JavaScript interpreter
will automatically look it up in obj
’s prototype. If
obj
’s prototype doesn’t have a value for p
either, the
search will continue in obj
’s prototype’s prototype, and so on. If
we eventually reach null
, which is not an object and therefore does not have
any properties, then obj.p
will evaluate to the special value
undefined
.
You can use a function called Object.create
to create an object that
delegates to another object. For example:
myShirt
does not have any properties of its own initially; it
inherits all of its properties from daddysShirt
.
This means that if we change the value of any of daddysShirt
’s
properties, e.g., with daddysShirt.numButtons = 8;
then
myShirt
’s value for that property will change, too.
Now let’s see what happens when we assign to one of the properties of
myShirt
:
myShirt
gets its own color
property
whose value is 'blue'
. This property shadows
the property that myShirt
previously inherited from daddysShirt
,
so future changes to daddysShirt.color
will no longer effect the value of
myShirt.color
.
To declare a function, you use the function
keyword:
add
in the current lexical scope.
Functions are first-class values. This means you can store a function in a variable,
property, or an array. You can also return a function from another function or a method. Note
that you don’t have to declare a function in order to get your hands on a function
value. In JavaScript, there are two ways to write function expressions. One looks similar to
a function declaration:
6
.[2, 3, 4]
.
Here’s a more elaborate example that shows that a function can reference variables
from its enclosing environment:
0
.1
.2
.
In JavaScript, you can call a function with any number of arguments, no matter how many
formal parameters that function has in its declaration. A function can access
the arguments that were passed to it via its formal parameters or the arguments
object, which is, erm, not entirely unlike an array. The arguments
can be used
to write variadic functions, i.e., functions that take a variable number of arguments.
For example:
13
.
Warning! The arguments
object is array-like, but it’s not really an
array. This is unfortunate for a number of reasons, not least of all because it means that
arguments
doesn’t support useful Array
methods like
map
and reduce
. If you really want to use those methods,
here’s a way to create an array that contains the arguments that were passed to your
function:
slice
and
call
methods later.
JavaScript has
lexical scoping, but it doesn’t work the way you’d expect. You
see, in most languages that are lexically-scoped, a block statement
({
… }
) is a lexical scope. In JavaScript, the only thing
that is a lexical scope is a function.*
catch
blocks also get their own lexical scope.
A method is just a function that’s stored in a property. For example, if we have:
aPoint.toString()
will evaluate to '(-2,5)'
.
To evaluate a method call
erecv.
m(
e1,
e2,
…,
en)
, a JavaScript interpreter will:
this
bound to the receiver.
Warning! When you call a function o.m(1, 2)
.f(1, 2)
, this
gets bound to undefined
. So if you
declare a helper function inside of a method — which seems like a reasonable thing
to do! — it won’t work as you would expect:
self
through which the helper function can access the receiver:
helper
a fat arrow function. Here’s
what that looks like:
this
from its enclosing lexical scope.
So far we’ve seen two ways to call a function,
f(1, 2)
, andobj.m(1, 2)
this
for each of them.
There are two more ways to call a function in JavaScript that are worth mentioning. One is
through the function’s call
method, which is useful because it lets you
specify the object that should be bound to this
when the function’s
body is evaluated, e.g., f.call(objThatWillBeThis, 1, 2)
.
The other way is through the function’s apply
method, which is like
call
except that you pass the arguments as an array, e.g.,
f.apply(objThatWillBeThis, [1, 2])
. This is useful if, at compile time, you
don’t know how many arguments your code will pass to a method or function. As an
example, here’s a function that takes a function as an argument, and returns another
function that behaves just like the original, but it also logs every call to the console:
logged
works with any function, no matter the arity
(number of formal parameters): 'inc was called'
to the console and returns 6
.
'add was called'
to the console and returns
3
.
JavaScript does not have support for classes, but it does have a syntactic sugar
for declaring them. Under the hood, it’s still all just objects, delegation, etc.
but the sugar really makes it feel like you’re programming in a “classy”
language. Here’s an example of a class declaration:
new Point(1, 2)
:
prototype
property, e.g., Object.create(Point.prototype)
.
Note that when you write a function, its prototype
property automatically
gets initialized to a constructor
, whose value is the
function itself.
new Object()
.this
bound to the new
object.new
expression will be that new object, unless the
function returns an object. (In which case, that object will be the value of the
new
expression.)
The relationships among the Point
“class”, the Point
prototype, and Point
instances are illustrated on the right. Note that all
Point
s delegate to Point.prototype
, which makes it the ideal
place to store whatever properties they share, e.g., methods like toString
.
You can see how this is done in the desugaring above.
Here’s what it looks like when you declare a subclass:
Point
and Point3D
“classes”, their prototypes, and their instances are illustrated below.
var
vs. const
if
, while
, for
,
try
/catch
, switch
, …Functional Programming
[1,2,3].map(x => x + 1)
evaluates to [2,3,4]
.[1,2,3].reduce((x, y) => x + y, 0)
evaluates to 6
.[1,2,3].filter(x => x > 1)
evaluates to [2,3]
.Meta Stuff
eval
function. Calling eval
is generally frowned upon (it’s a huge
security hole!) but it can be very useful when you’re prototyping a language via
source-to-source translation.obj
has a property p
that’s not
inherited from its prototype: obj.hasOwnProperty('p')
.obj
’s “own property”
names: Object.keys(obj)
.Numbers
n
is a number: typeof n === 'number'
.
Note that I’m using the strict equality operator (===
), not
the completely f***ed-up equality operator (==
). No one should
ever use the latter, because it doesn’t make any sense whatsoever.
(See this page to
see just how crazy ==
is.)
true
even for values like
Number.POSITIVE_INFINITY
and Number.NaN
.
n
is finite: isFinite(n)
.n
is not a number:
isNaN(n)
.parseInt
and parseFloat
functions.
NaN
if the argument can’t be parsed.parseInt('23abc') === 23
.Arrays
arr
is an Array
:
Array.isArray(arr)
.arr
: arr.length
.
length
property.i
th element: arr[i]
.
undefined
if i
is out of bounds.0
-based.i
th element: arr[i] = value
.
i
≥ arr.length
, arr.length
is updated
automatically.newElement
to the end: arr.push(newElement)
.arr.pop()
newElement
to the beginning: arr.unshift(newElement)
arr.shift()
.newElement
at idx
:
arr.splice(idx, 0, newElement)
.arr.forEach(x => …)
.Strings
typeof obj === 'string'
.'hello world'
and "foo"
are both valid string literals.length
property of a string tells you how many characters are in
it.String
indices are 0
-based.s[5]
) or the
charAt
method (s.charAt(5)
).
String
of length
1
.s.indexOf(otherString)
returns the index of the first occurrence of
otherString
in s
, or -1
if it’s not
found.s.substr(startIdx, length)
returns a substring of s
.Thanks to Marko Röder for detailed feedback on this document. This JavaScript tutorial was originally developed by Alex Warth and Todd Millstein for the Prototyping Programming Languages course at UCLA; used here by permission.