Getting Started: JavaScript Primer or: How I learned to stop worrying and love the web browser

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.

Motivation

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:

In this class, you will learn how to write your own source-to-source translators, like the compilers of these languages. As you probably already know, there are newer and trendier alternatives to JavaScript out there. For example, CoffeeScript and TypeScript are languages that compile to JavaScript and give the programmer access to all of the good stuff that’s in the web browser. While we have nothing against these languages, we encourage you to do our assignments in plain old JavaScript. It is, after all, the “assembly language” of the web, so a programming language hacker should know it well. The people who implemented CoffeeScript and TypeScript certainly do.

Alright, that’s enough motivation. Let’s dive in.

Objects

JavaScript’s objects are dictionaries that map property names to values. You can create an object by calling a constructor, e.g., new Object() or by writing an object literal, e.g., var daddysShirt = { type: 'polo', color: 'white', numButtons: 3 };

To access and assign values to the properties of an object, you use the . operator, e.g., Evaluates to 'white'.daddysShirt.color and daddysShirt.type = 'guayabera'; daddysShirt.numButtons = 7; daddysShirt.brand = 'Banana Republic'; When you assign a value to a property of an object, that property will be added automatically if the object doesn’t already have it. And if you don’t know the name of a property at compile time, there’s also a square bracket syntax that lets you compute the name dynamically, e.g., var propName = 'num' + 'Buttons'; Evaluates to 7.daddysShirt[propName]; Property names are always strings. If you try to access or assign to a non-string property of an object, e.g., var someObject = { foo: 42, bar: 'hello world' }; daddysShirt[someObject]; the non-string value will be implicitly converted to a string via its toString() method. In the admittedly nonsensical example above, someObject’s toString() returns '[object Object]'. Yup, the default toString() is pretty useless. Since 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, afterdelete daddysShirt.color; the expression daddysShirt.color will evaluate to undefined.

Delegation

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.)

When people say that an object delegates to its prototype, they’re talking about the property look-up operation. Here’s how the prototype chain is used for property look-up: if you look up the value of a property 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: // My shirt is like daddy’s shirt… var myShirt = Object.create(daddysShirt); The relationship between the two objects is illustrated on the right. Note that 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: // … except it’s blue. myShirt.color = 'blue'; After the assignment above, 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.

Functions

To declare a function, you use the function keyword: function add(x, y) { return x + y; } More on scoping later. The example above declares a function called 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: Evaluates to 6.(function(x) { return x + 1; })(5) The other, known as a fat arrow function, is more concise: Evaluates to [2, 3, 4].[1, 2, 3].map(x => x + 1) (There is another important difference between “normal” functions and fat arrow functions, which we discuss in the section on methods.)

Here’s a more elaborate example that shows that a function can reference variables from its enclosing environment: function makeCounter() { var count = 0; return function() { return count++; }; } var counter = makeCounter(); Evaluates to 0.counter(); Evaluates to 1.counter(); Evaluates to 2.counter();

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: function sum(/* a, b, c, … */) { var ans = 0; for (var idx = 0; idx < arguments.length; idx++) { ans += arguments[idx]; } return ans; } sum(5, 10, -2);Evaluates to 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: var args = Array.prototype.slice.call(arguments); It’s kludgy, but it does the trick. More on the slice and call methods later.

Scoping

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.* * That’s not completely true, I used a little didactic license there: catch blocks also get their own lexical scope.

So when you write this: function f(x) { if (x > 5) { var y = x * x; … } … }
What it really means is this: function f(x) { var y; if (x > 5) { y = x * x; … } … }
Usually this isn’t a problem, but it’s something you should be aware of. And if you’re used to shadowing variable declarations, get ready to spend countless hours debugging your programs and cursing Brendan Eich!

Methods

A method is just a function that’s stored in a property. For example, if we have: var aPoint = { x: -2, y: 5, toString: function() { return '(' + this.x + ',' + this.y + ')'; } }; then aPoint.toString() will evaluate to '(-2,5)'.

To evaluate a method call erecv.m(e1, e2,, en), a JavaScript interpreter will:

Warning! When you call a function the usual way, e.g., As opposed to a method call, e.g., 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: ({ m1: function() { // here, `this` is the receiver this.m2(); // works function helper() { // but here, `this` is `undefined` this.m2(); // ERROR! } helper(); }, m2: function() { … } }).m1(); This is a subtle bug, and JavaScript programmers get bit by it all the time. The usual work-around, once you realize that this is the problem, is to declare a local variable called self through which the helper function can access the receiver: ({ m1: function() { var self = this; function helper() { // here, `this` is still `undefined` // but we have access to the receiver through `self` self.m2(); // works! } helper(); }, m2: function() { … } }).m1(); Another solution is to make helper a fat arrow function. Here’s what that looks like: ({ m1: function() { var helper = () => this.m2(); // works! helper(); }, m2: function() { … } }).m1(); This works because a fat arrow function captures not only the local variables, but also this from its enclosing lexical scope.

Two More Ways to Call a Function

So far we’ve seen two ways to call a function,

and we’ve seen what happens with 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: function logged(f) { return function() { console.log(f.name + ' was called'); return f.apply(this, arguments); }; } Note that logged works with any function, no matter the arity (number of formal parameters): function inc(x) { return x + 1; } var loggedInc = logged(inc); loggedInc(5);Outputs 'inc was called' to the console and returns 6. function add(x, y) { return x + y; } var loggedAdd = logged(add); loggedAdd(1, 2);Outputs 'add was called' to the console and returns 3.

“Classes”

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: class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ',' + this.y + ')'; } } And here’s its desugaring, i.e., what it really means: function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function() { return '(' + this.x + ',' + this.y + ')'; }; To understand how this works, you first have to know what happens when you call a function like a constructor, e.g., new Point(1, 2):

The relationships among the Point “class”, the Point prototype, and Point instances are illustrated on the right. Note that all Points 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.

Inheritance, super-sends, etc.

Here’s what it looks like when you declare a subclass: class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; } // Override toString() { return '(' + this.x + ',' + this.y + ',' + this.z + ')'; } } And here’s (roughly) its desugaring: function Point3D(x, y, z) { // Call Point's constructor with `this` bound to the new Point3D. Point.call(this, x, y); // Finish initializing the new instance. this.z = z; } // Make Point3D's prototype delegate to Point's prototype, that // way it will inherits all of the methods of the superclass. Point3D.prototype = Object.create(Point.prototype); Point3D.prototype.toString = function() { return '(' + this.x + ',' + this.y + ',' + this.z + ')'; }; The relationships among the Point and Point3D “classes”, their prototypes, and their instances are illustrated below.

Playground
Other Things Worth Talking About, a.k.a. TODO

Reference

Functional Programming

Meta Stuff

Numbers

Arrays

Strings

Acknowledgments

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.