Why JavaScript?
There are lots of programing languages. At time of writing, the Tiobe index ranks Python as the most popular, with JavaScript trailing at number six.
A big advantage JavaScript has over all its rivals is it comes pre-installed in your browser, so for webapps such as the games I’m putting on this website, there’s no alternative.
Some try to treat JavaScript like assembly language, preferring server-side programing languages which compile into JavaScript to pass to your browser. I’ve gone down that cul-de-sac, and found modern JavaScript way better.
An important tool to learning JavaScript is Crockford’s jslint.com. Initially I thought Crockford’s prejudice against this silly, but with more experience have learnt to never use it in my code.
What makes JavaScript wonderful is its relationship to Crockford’s JSON which I expand on in Object Literals and Array Literals, the two key workhorses.
Nowadays, the definitive guide is developer.mozilla.org which besides JavaScript, is also an encyclopaedic reference for HTML and CSS, which go hand-in-glove.
Types
As explained in testing, the best way to learn a language is to write lots of tests which help as documentation later. This one illustrates JavaScript’s basic native types:
it ("functions are types", function() {
expect(typeof ((a, b) => a + b)).toBe("function");
expect(typeof true).toBe("boolean");
expect(typeof "Hello World").toBe("string");
expect(typeof 123).toBe("number");
expect(typeof {"key": "value"}).toBe("object");
expect(typeof [1,2]).toBe("object");
expect(typeof (new Set([1,2]))).toBe("object");
expect(typeof null).toBe("object");
expect(typeof undefined).toBe("undefined");
});A nice thing about types is there’s usually an algebra (or what was historically known as a calculus before the word became synonymous with differential calculus) that geniuses of yore have simplified for us. While the algebra of numbers is well known, there’s boolean algebra which I’ve elaborated on for JavaScript in an article on logic.
Thanks to Alonzo Church and Haskel Curry, there’s an algebra of functions, encouraging good composition through things like callbacks in JavaScript.
That text is called string in most computer languages is a slipshot extension of paths in state-machines. Thanks to Stephen Cole Kleene, there an algebra of strings.
It’s really unfortunate that there’s no array and set type, and that object literals aren’t called dictionaries as in Python. In JavaScript, these have all been chucked into a bin called object. Intead of types, they are class instances — a real pigs breakfast I rant about in class inspection below.
I made up the term “object-oriented”, and I can tell you I did not have C++ in mind. — Alan Kay
Functions are “first class” types
Mercifully, functions were not lumped into objects. A
powerful idea JavaScript borrowed from Lisp was to make functions a
type, allowing them to be used as variable names like any other in
argument lists of other functions.
Structure and Interpretation of Computer Programs defines first-class elements (types in JavaScript jargon) as having these four properties:
- They may be named by variables.
- They may be passed as arguments to functions.
- They may be returned as the results of functions.
- They may be included in data structures.
In JavaScript, functions tick all the above four boxes.
Callbacks
Variables whose type is “function” passed as arguments to other functions (ie #2 in the above list) are called callbacks in JavaScript.
Common examples of functions that have functions as arguments are:
- EventListener.addEventListener(type, callback)
- Array.forEach(callback)
- Array.map(callback)
- Array.filter(callback)
- Array.reduce(callback, initialValue)
- Array.sort(callback)
- Promise.then(callbackSuccess, callbackReject)
- Window.requestAnimationFrame(callback)
Why allowing functions to be arguments in the above functions is so powerful is it enables us to abstract the above into doing most common computing tasks.
Naming functions as variables
Per tradition with JavaScript, there are several synonymous ways of defining a variable name to be a function. The two I find of practical use are function expressions and function declarations.
it("function expressions", function() {
const double = function (x) {
return 2 * x;
};
const myArr = [1,2,3,4];
expect(myArr.map(double)).toEqual([2,4,6,8]);
});
it("function declaration", function() {
function double(x) {
return 2 * x;
}
const myArr = [1,2,3,4];
expect(myArr.map(double)).toEqual([2,4,6,8]);
});Anonymous (aka lambda) functions
Variables substitute themselves into literal values. To save a few bytes, literals can be used directly as arguments in functions, skipping the need to first allocate them to variable names. The same applies to functions:
it("anonymous function", function() {
const myArr = [1,2,3,4];
expect(myArr.map(function (x) {
return 2 * x;
})).toEqual([2,4,6,8]);
});JavaScript has arrow notation to make short, simple functions even more terse:
it("arrow function", function() {
const myArr = [1,2,3,4];
expect(myArr.map((x) => 2 * x)).toEqual([2,4,6,8]);
});Using literals in code directly rather than assigning them to variables is frowned upon as magic numbers. Even if it seems obvious that that’s the sole possible value when you initially do the coding, if you later find you do need to change something used many times across several modules, pinning it once to a variable/constant name is a lot cleaner. This is especially true for functions because they are more fiddly than atomic values.
Where do callbacks get their arguments?
A big confusion for me initially was if I’m just using a function’s variable name, where do its arguments come from and how many are there? Furthermore, what does the callback return?
It turns out that varies from one function that takes callbacks as arguments to another:
-
EventListener.addEventListener(type, callback) -
Here
callbackhas one argument, an instance of an event which has lots of classes according to type. I mainly use pointer events which I cover in an article. No return value is expected. -
Array.forEach(callback) -
This has three arguments,
callback(element, idx, array). JavaScript doesn’t require functions to be called with all arguments. The more rarely they are needed, the further down the list they go. No return value is expected. -
Array.map(callback) -
Same as above,
callback(element, idx, array). Here callback is expected to return a value. -
Array.filter(callback) -
Again,
callback(element, idx, array)with a boolean expected as the return value. -
Array.reduce(callback, initialValue) -
This needs an extra argument,
callback(accumulator, element, idx, array), and it returns an updated accumulator every iteration. -
Array.sort(callback) - The callback takes two values and returns either a negative number, zero, or a positive number.
-
Promise.then(callbackSuccess, callbackReject) -
The promise provides either of its callbacks with one value which can be
any type. A chain of promises can be made, with each callback returning
a promise for the next
.then’s callbacks to handle. -
Window.requestAnimationFrame(callback) -
I used this with RiceRocks to animate sprites
on a canvas in the fairly typical way of creating an infinite loop by
having the callback be the same function that
requestAnimationFrameis the final statement in. One argument, a timestamp, is sent to the callback.
How to use a callback in your own function
it("Using a callback in your own function", function() {
function double(x) {
return 2 * x;
}
function myFunc(callback, x) {
return callback(x);
}
expect(myFunc(double, 2)).toBe(4);
});Functions vs Mutators
Unlike, say Pascal, which makes a distinction between
functions and procedures, JavaScript
procedures are simply functions without return statements. Typically,
they alter values of objects passed as arguments rather than return
anything.
it("map vs forEach", function() {
const myArr = [1,2,3,4];
expect(myArr.map((x) => 2 * x)).toEqual([2,4,6,8]);
myArr.forEach(function(x, idx) {
myArr[idx] = 2 * x;
});
expect(myArr).toEqual([2,4,6,8]);
});In the above example, the values in myArr are doubled in
both examples. But an important difference is myArr isn’t changed by
map, while forEach mutates the original as explained in array literals.
Though myArr[idx] substitutes into x, vice
versa isn’t true. A nooby mistake I made was thinking this would
work:
myArr.forEach(function(x) {
x = 2 * x;
});Only x gets updated. Nothing happens to myArr.
Though frowned on by functional programers who refer to mutation as side effects, they are vital to modularizing games like the ones on this site.
Crockford refers to functions such as
myArr.sort(callback) which mutate myArr rather than return
an altered copy as impure, and I’ve tripped up doing stuff like
myArr = myArr.sort(callback), thereby setting
myArr to undefined instead of a sorted array.
But once one remembers which are mutators and which are
pure, it’s fairly easy.
Class Inspection
Object-oriented programming is an exceptionally bad idea which could only have originated in California. — Edsger Dijkstra
When typeof tells you something is an
object, the next challenge is to figure out what class it’s
an instance of. JavaScript seems to be lacking a simple way of doing
class introspection. About the best way I’ve figured out how to
get an object’s class is to use its toString
method:
it ("toString.call(Object)", function() {
expect(toString.call(((a, b) => a + b))).toBe("[object Function]");
expect(toString.call(true)).toBe("[object Boolean]");
expect(toString.call("Hello World")).toBe("[object String]");
expect(toString.call(123)).toBe("[object Number]");
expect(toString.call({"key": "value"})).toBe("[object Object]");
expect(toString.call([1,2])).toBe("[object Array]");
expect(toString.call(new Set([1,2]))).toBe("[object Set]");
expect(toString.call(null)).toBe("[object Null]");
expect(toString.call(undefined)).toBe("[object Undefined]");
});Where I really stared into the horror of OOP was figuring out HTMLElements.
it("Using toString to find an object's class", function() {
const body = document.querySelector("body");
expect(toString.call(body)).toBe("[object HTMLBodyElement]");
});One of the supposed benefits of classes is they inherit from other classes, and figuring out HTMLBodyElement’s ancestry involves playing a game of 20 questions using boolean operator instanceof.
it("instanceof body examples", function() {
const body = document.querySelector("body");
expect(body instanceof EventTarget).toBe(true);
expect(body instanceof Node).toBe(true);
expect(body instanceof Element).toBe(true);
expect(body instanceof HTMLElement).toBe(true);
expect(body instanceof HTMLBodyElement).toBe(true);
});The next challenge is to figure out what all the properties of HTMLBodyElement are, and again JavaScript lacks any simple, clean solution.
In MDN, I found this suggestion:
function listAllProperties(myObj) {
let objectToInspect = myObj;
let result = [];
while (objectToInspect !== null) {
result = result.concat(Object.getOwnPropertyNames(objectToInspect));
objectToInspect = Object.getPrototypeOf(objectToInspect);
}
return result;
}Using this on HTMLBodyElement just tells me it has unmanageably many.
it("all properties length", function() {
const body = document.querySelector("body");
const properties = listAllProperties(body);
expect(properties.length).toBe(371);
});A lot of 371 those properties duplicate stuff already done previously in the class hierarchy. This is one of the many reasons I follow Crockford’s advice to do “class free” JavaScript, never creating my own classes and only using the built in ones if there’s no alternative.