Introduction to JavaScript

JavaScript is probably the most popular programming language in the world.

Overview

JavaScript, the language:

JavaScript was originally named Mocha and the first version was written by Brendan Eich in 10 days in May of 1995. The name was changed to LiveScript in September, 1995, and became JavaScript sometime after that.

Although over 20 years old, JavaScript is still an evolving language. A lot of the code in the wild, and a lot of the code you see online and in books, is quite old and may not follow modern best practices. This is just the way the world is.

You’ll definitely want to see:

Getting Started

You can run JavaScript programs (scripts) from the command line or directly within a web browser.

Find your browser’s “Developer Tools” and go to the “Console” tab. At the prompt, enter the one-line program:

console.log("Hello, world.");

This script consists of one statement which is a call to the function that is the value of the property named log of the property named console of the global object.

The browser’s console is not the main show! You normally want to write to the browser’s output panel. The browser contains a document with a body that can show either plain text (through its textContent property) or beautiful structured content (through its innerHTML property). Run this code from the console:

document.body.textContent = "Hello, world.";

Ohai! You made it back! That program blew away this page that you were looking at, didn’t it? Don’t do that again. If you are going to play around and learn JavaScript, go to some cool place like CodePen first.

Exercise: Try that script at CodePen.

Here’s a more sophisticated script:

See the Pen Cm to Inches by Ray Toal (@rtoal) on CodePen.

CodePen is great for experimentation and development. When you are ready to deploy code, you’ll need to use files and take over the entire browser pane, not just the output pane in CodePen. Use a text editor to create these three files. Load the HTML file into your browser and run this interactive program:

bmi.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>BMI Calculator</title>
    <link rel="stylesheet" href="bmi.css">
  <head>
  <body>
    <h1>Find Your BMI</h1>

    <p>Weight in pounds <input type="text" id="weight"></p>
    <p>Height in inches <input type="text" id="height"></p>
    <p>The BMI is <span id="answer"></span></p>

    <script src="bmi.js"></script>
  </body>
</html>
bmi.css
h1 {
  color: white;
  background-color: teal;
  font: lighter 42px "Avenir Next";
  margin-top: 0;
  padding: 13px;
}

p {
  color: cornflowerblue;
}

body {
  background-color: linen;
  text-align: center;
  margin: 0;
}
bmi.js
const KILOGRAMS_PER_POUND = 0.453592;
const METERS_PER_INCH = 0.0254;

const heightBox = document.querySelector('#height');
const weightBox = document.querySelector('#weight');
const answerSpan = document.querySelector('#answer');

heightBox.addEventListener('input', updateBMI);
weightBox.addEventListener('input', updateBMI);

function updateBMI() {
  const pounds = +weightBox.value;
  const inches = +heightBox.value;
  const kilograms = pounds * KILOGRAMS_PER_POUND;
  const meters = inches * METERS_PER_INCH;
  const bmi = kilograms / (meters * meters);
  answerSpan.textContent = bmi.toFixed(2);
}

bmi.png

Now here is a JavaScript program that you can put into a .js file and run from the command line:

powers.js
const limit = Number(process.argv[2]);

for (let x = 1, i = 0; i <= limit; i += 1) {
  console.log(`2^${i} = ${x}`);
  x += x;
}
$ node powers.js 5
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32
Exercise: Write your own (a) CodePen-based application, (b) file-based application, (c) command-line application.

Now that you’ve seen what JavaScript looks like, we are ready to learn JavaScript from the beginning.

Data Types

All values belong to one of 7 types:

TypeValues of the Type
UndefinedOnly one value: undefined. Means “I don’t know,” “I don’t care”, or “None of your business.”
NullOnly one value: null. Means “no value.”
BooleanOnly two values: true and false.
NumberThe IEEE 754 floating point values. Values that are integers can be expressed in binary, octal, decimal, or hex; non-integers must be expressed in decimal. Examples:
  • 8
  • 7.23342
  • 6.02e23
  • 0xff3e
  • 0b11010100001010
  • 0o237
  • Infinity
  • NaN
StringSequences of zero or more UTF-16 code units. You can delimit them with apostrophes, quotation marks, or backticks. Examples:
  • "hello"
  • "She said 'I don’t think so 😎'... (╯°□°)╯︵ ┻━┻)"
  • 'x = "4"'
  • "abc\tdef\"\r\nghi\n🏄‍♀️🏀\n"
  • "Olé"
  • "Ol\xe9"
  • 'Will I?\u043d\u0435\u0442\u263a'
  • `The sum of ${x} and ${y} is probably ${x + y}`
Strings are immutable. Only backtick-delimited literals can span lines and support string interpolation.
SymbolUnique things. Every time you create a symbol, you get a new thing, different from all other symbols. This is not necessarily true of strings. Examples:
  • Symbol()
  • Symbol('dog')
  • Symbol('dog') // different from the one above
ObjectEverything that isn’t one of the above six types. Examples:
  • {}
  • {latitude: 74.2, longitude: -153.11}
  • [true, true, {last: false, value: 'okay'}, [0, 0, 2]]
  • new Set([5, 1, 2])
  • new Date(2000, 12, 31)
  • (x, y) => x * x + y * y
  • /Boo+m!?/gi
Notice even though you can see arrays, functions, regular expressions, sets, dates, and similar things, to JavaScript, each of these values has the type Object. If you want a richer type system you need to go to a different language.

Built-in operations by type

Some common operations on numbers:

Some common operations on strings:

These work on numbers and strings:

These work on any value:

x === yproduces whether x and y are EQUAL
x !== ysame as !(x === y)
x == yproduces whether x and y are SIMILAR (do not use!!)
x != ysame as !(x == y) (do not use!!)
x && yif x is falsy then x, else y
x || yif x is truthy then x, else y
!xif x is truthy then false, else true
Soon there will be 8 types.

An eighth type, bigint, will soon be officially part of the language. It is already supported by recent versions of the major browsers as well as in Node.js.

Determining the type of an expression

In JavaScript, types are not objects in their own right; instead, if you ask for the type of an object, you will get a string. Unfortunately, the normal way you do this is fairly messed up:

typeof undefined      // 'undefined'
typeof null           // 'object'   -- WTF??? THIS IS A LANGUAGE DESIGN FLAW
typeof false          // 'boolean'
typeof 93.8888        // 'number'
typeof "Hi"           // 'string'
typeof {x: 1, y: 2}   // 'object'
typeof [1, 2, 3]      // 'object'   -- sensible, arrays are just objects
typeof /o*h/          // 'object'   -- sensible, regexes are just objects
typeof (x => x + 1)   // 'function' -- grrrr, functions are actually objects too
typeof NaN            // 'number'   -- sounds hilarious but makes sense

Weak typing

Usually, if an operator doesn’t get operands of the right type, it does conversions to get arguments of the right types. This means JavaScript is weakly typed. (In a strongly typed language, on the other hand, operands of the wrong type generate an error.)

When a boolean is expected:

When a number is expected:

When a string is expected:

A value that is false or would be converted to false is called falsy. All other values are truthy. (I did not make up those names.)

Exercise: For each of the following values, state whether they are truthy or falsy: 9.3, 0, [0], false, true, "", "${''}", `${''}`, 9.3, [], [[]], {}.
JavaScript is SO weakly typed...

...that type errors hardly ever occur. As far as I know, the only ways you can get a type error (without explicitly throwing one yourself) are by using a symbol where a number or string is expected and trying to access a property on null or undefined.

Bindings

You can bind names to expressions, and use the names later, for example:

const dozen = 3 * 4;         // Binds the name dozen to the value 12
let favorite = 'Grimes';     // Binds the name favorite to the value "Grimes"
dozen = 2;                   // Throws a TypeError because const bindings cannot be updated
console.log(dozen);          // 12
favorite = 'Austra';         // That's fine! let-bindings can be updated
console.log(favorite);       // Austra

Use const when the value bound to the name should not and must not change; use let otherwise. It is considered good programming practice to use const almost all the time. If you are updating a binding, ask yourself whether that is the best thing to do.

You can also bind with var, but don’t.

We’ll see why later.

By the way, the names you bind to values are called variables.

Objects

Undefined, Null, Boolean, Number, String, and Symbol are called primitive types. Object is a reference type, meaning object values are references. Objects have properties which are key-value pairs. Keys can be strings, numbers, or symbols. Create and access the properties with square bracket notation, or, if the key is a string of simple-enough characters, with dot-notation. You can even delete properties.

const x = {};
x.age = 17;
x.height = 65.3;
const score = x.age * x["height"];
const z = {age: 30, color: "red", total: 3};
z.last = true;
const rat = {triple: {a: 4, b: undefined, c: {4: null}}, 7: "stuff"};
delete z.age;

Notice how the values of primitives are ”stored directly” while the values of objects are the pointers, not the objects themselves:

jsobjects.png

The fact that object values are actually pointers has major ramifications for assignment and equality testing that you absolutely must understand:

const a = {x: 3, y: 5};   // The curly braces CREATE a new object
const b = {x: 3, y: 5};   // The curly braces CREATE a new object
const c = b;              // Copy the arrow in box b into the box for c
a !== b;                  // Because these two arrows point to different things
b === c;                  // Because these two arrows point to the same thing
Exercise: (IMPORTANT) Draw pictures of these variables and objects and explain how the picture illustrates the meaning of object assignment and equality.

There’s some convenient syntax surrounding objects that is good to know:

const x = 10;
const c = {x, y: 3}        // x is the same as x:x, so {x: 10, y: 3}
const o = {a: 5, b: 4, c}; // {a: 5, b: 4, c: {x: 10, y: 3}}
const {b, c: {y}} = o;     // This is called destructuring. Now b is 4 and y is 3

Prototypes

Every object in JavaScript has a prototype. When looking up properties, the prototype chain is searched. Objects created with the { } syntax all get the same built-in prototype (more on this later), but you can alternatively set the prototype when the object is created, with Object.create:

const protoCircle = {x: 0, y: 0, radius: 1, color: "black"};
const c1 = Object.create(protoCircle);
c1.x = 4;
c1.color = "green"; // Now c1.y === 0 and c1.radius === 1

jsobjwithproto.png

Here $c1$ has two own properties: x and color. It has several inherited properties, including y and radius from its own prototype, and a bunch of others from its prototype’s prototype.

Prototypes are very useful when creating a bunch of objects that look or behave similarly:

jsobjswithsharedproto.png

Arrays

An array is an object constructed with square brackets or Array(...) or new Array(...). It has a length property as well as non-negative integer properties.

const a = [];
let b = [1, 5, 25];
let c = new Array();          // Same as []         (DON'T DO THIS)
c = new Array(4, 5, 6);       // Same as [4, 5, 6]  (DON'T DO THIS)
c = new Array(10);            // 10 empty cells
const [d, e, f] = b;          // d===1, e===5, f===25 (destructuring)
[c, b] = [b, false];          // c===[1, 5, 25], b===false

const g = [1, true, [1,2], {x:5, y:6}, "Hi"];
const h = {triple: {a:4, b:"dog", c:[1,null]}, 7: "stuff"};

g[1];          // true
g[2][0];       // 1
g.length;      // 5
g[10] = 100;   // g[5] through g[9] are empty cells
g.length;      // 11
g.length = 2;  // Now g is just [1, true]
Exercise: Experiment with arrays. Can we get to the third element of array dogs by saying dogs.2 or dogs["2"]? Or is dogs[2] the only way? What about dogs."2" or let x=2; dogs.x?
Exercise: Experiment some more with arrays. Does let x = ["dog"] define a one element array? What if we create a three-element array and try to print the value of its 12th element? What if we create a three-element array and set the value of its 12th element.
Exercise: What’s going on here?

let a = new Array(3);
a[0] = 1;
a[1] = "hello";
a[2] = a;

Here are some array operations:

// Construction
const a = [300, 900, 200, 400, 300, 700];
const b = [500, 300];
const c = Array.of(95, 33, 'dog', false); // [95, 33, 'dog', false]
const d = Array.from('dog');              // ['d', 'o', 'g']
const e = [5, ...b, -10]                  // [5, 500, 300, -10] (note the spread operator)
const f = [b, ...b]                       // [[500, 300], 500, 300]

// Tests
Array.isArray(['x', true, 3]);            // true
Array.isArray({0: 'x', 1: true, 2: 3});   // false

// Accessors (these do not change their operands)
a.includes(400)      // true
a.includes(7)        // false
a.indexOf(300)       // 0
a.lastIndexOf(300)   // 4
a.indexOf(2500)      // -1
a.slice(2, 5)        // [200, 400, 300]
a.slice()            // makes a shallow copy!
a.concat(b)          // [300, 900, 200, 400, 300, 700, 500, 300]
a.join('--')         // "300--900--200--400--300--700"
a.toString()         // '300,900,200,400,300,700'

// Mutators
a.push(3)            // returns 7, a is now [300, 900, 200, 400, 300, 700, 3]
a.pop()              // returns 3, a is now [300, 900, 200, 400, 300, 700]
a.unshift(8)         // returns 7, a is now [ 8, 300, 900, 200, 400, 300, 700 ]
a.shift()            // returns 8, a is now [300, 900, 200, 400, 300, 700]
a.reverse()          // a is now [ 700, 300, 400, 200, 900, 300 ]
a.sort()             // a is now [ 200, 300, 300, 400, 700, 900 ]
a.fill(9)            // a is now [ 9, 9, 9, 9, 9, 9 ]
e.splice(1, 2, 90, 70, 200, 6, 17);
                     // At position 1, remove 2 elements, and insert the others
                     // e is now [ 5, 90, 70, 200, 6, 17, -10 ]
e.copyWithin(1, 4, 6)
                     // shallow copy elements at positions [4, 6) to position 1
                     // e is now [ 5, 6, 17, 200, 6, 17, -10 ]

// Really Cool stuff
c.keys()             // An iterator that goes through 0, 1, 2, 3
[...c.keys()]        // [0, 1, 2, 3]
Array.from(c.keys()) // [0, 1, 2, 3] (though use the spread instead)
[...c.entries()]     // [[0, 95], [1, 33], [2, 'dog'], [3, false]]

for (let [i, x] of c.entries()) console.log(`${i} -> ${x}`);
                     // 0 -> 95
                     // 1 -> 33
                     // 2 -> dog
                     // 3 -> false
Exercise: Experiment with splice. Show that you can use it for inserting elements only, deleting elements only, or replacing elements only.
Exercise: Experiment with copyWithin. What if the target range overlaps the source range? What if (any of) the indexes are out of bounds? How are negative indexes handled?

You will sometimes see Array.from when programming in the web browser. Things like NodeLists and HTMLCollections look like arrays but they aren’t; use Array.from to turn them into real arrays if you need to do such things.

The array operations find, findIndex, map, filter, reduce, every, and some will be discussed later.

Functions

Functions come in two flavors: they are either arrow functions or non-arrow functions. Here are some arrow functions in action:

(x => x * 2 + 1)(20)                                    // evaluates to 41
const square = x => x * x;                              // can also use let or var
const now = () => new Date();
const average = (x, y) => (x + y) / 2;
average(3, -4)                                          // evaluates to -0.5

Non-arrow function values the function keyword, the Function constructor, or are created in a function declaration. When inside an object, you can use a shortcut syntax that also omits the function keyword:

function successor (x) {
  return x + 1;
}
const sum = function (x,y) {return x + y;}
const predecessor = new Function("x", "return x - 1;"); // Don’t actually use this

const x = {
  f: (a) => a * 3,                  // arrow function
  g: function (a) {return a * 3;},  // NOT an arrow function
  h(a) {return a * 3;}              // NOT an arrow function
}

A function value is fully first-class in JavaScript meaning:

Furthermore a function is an object so:

Higher-Order Functions

Functions that accept functions as parameters, or that return functions, are called higher order functions. This should be no big deal. We had higher order functions in the 1950s in Lisp, but it took many decades for the whole world to realize how awesome they are.

const plusSix = x => x + 6;
const squared = x => x * x;
const twice = (f, x) => f(f(x));
twice(plusSix, 5);                                   // 17
twice(squared, 4);                                   // 256
const compose = (f, g) => (x => g(f(x)));
const squareThenPlusSix = compose(squared, plusSix);
squareThenPlusSix(10);                               // 106
compose(plusSix, squared)(5);                        // 121
`twice expects ${twice.length} arguments`;           // 'twice expects 2 arguments'
A bunch of array operations are higher-order:
const a = [3, 5, 2, 8, 21, 72];
a.map(x => x / 2);                       // [ 1.5, 2.5, 1, 4, 10.5, 36 ]
a.filter(x => x % 2 == 0);               // [ 2, 8, 72 ]
a.reduce((x, y) => x + y, 0);            // 111
a.every(x => x < 20);                    // false
a.some(x => x < 25);                     // true
a.find(x => x > 7);                      // 8
a.findIndex(x => x > 7);                 // 3

Parameter Passing

Passing an argument to a parameter is just like assignment. Nothing more, nothing less. But there are interesting things:

If you pass too many arguments, the extras are ignored:

function f(x, y, z) { return [x, y, z]; }
f(3, 1, 8, 5, 9);                            // [3, 1, 8]

If you pass too few arguments, the extras are undefined:

function f(x, y, z) { return [x, y, z]; }
f(3, 1);                                     // [3, 1, undefined]

Parameters can have defaults:

function f(x = 1, y, z = 3) { return [x, y, z]; }
f();                    // [1, undefined, 3]
f(5);                   // [5, undefined, 3]
f(5, 1);                // [5, 1, 3]
f(5, 10, 13);           // [5, 10, 13]
f(undefined, 2);        // [1, 2, 3]
f(undefined, 2, 100);   // [1, 2, 100]

The last parameter can be a rest parameter:

function f(x, ...y) { return [x, y]; }
f()               // [undefined, []]
f(1)              // [1, []]
f(1, 2)           // [1, [2]]
f(1, 2, 3)        // [1, [2, 3]]
f(1, 2, 3, 4, 5)  // [1, [2, 3, 4, 5]]

Since argument passing is just assignment, we can do destructuring:

function line({x1, y1, x2, y2, style='solid', thickness=1, color='black'}) {
  return `Drawing a ${color} ${style} line from (${x1},${y1}) to (${x2},${y2}) with weight ${thickness}`;
}
line({x2: 5, x1: 4, color: 'green', y1: 6, y2: 10});
    // 'Drawing a green solid line from (4,6) to (5,10) with weight 1'

Scope

Variables declared with var are local to the innermost enclosing function. When declared with const or let, they are local to the innermost enclosing block.

Variables are visible within their scope (this includes blocks and functions nested within that scope) and are invisible outside:

var a = 1;
let b = 2;
// c, d, e, and f are not in scope here
function example() {
  console.log(c); // undefined
  // If we tried to use d here, it would throw a ReferenceError
  var c = 3;
  let d = 4;
  console.log(e); // undefined
  // If we tried to use f here, it would throw a ReferenceError
  if (true) {
    var e = 5;
    let f = 6;
    console.log(a, b, c, d, e, f);
  }
  console.log(e); // 5
  // f out of scope, using it would throw a ReferenceError
}
// Only a and b are in scope here; c, d, e and f are no longer in scope
example();

Did you read that example carefully? Note that using a let or const variable in its scope but before the declaration (i.e., within the temporal dead zone) throws a ReferenceError. Doing that with a var variable just gives you undefined.

Here’s a major ramification of function scope versus block scope:

let a = [];
for (var i = 0; i < 10; i++) { a[i] = () => i; }
a[3]();                                              // 10, GRRRRR
for (let i = 0; i < 10; i++) { a[i] = () => i; }
a[3]();                                              // 3, YES I EXPECTED THAT
Exercise: Study that example.
NEVER USE var.

The var keyword is obsolete. Never use it. You should know how it works though, because you might see it in other people’s code.

Closures

When an inner function is sent outside its enclosing function, it is called a closure, because JavaScript retains the outer variables that live in a wrapping scope that “closes over” the function. Closures are crazy popular in JavaScript because local variables are one of the very few ways to do information hiding.

You will often see code like this:

const nextFib = (() => {
  let [a, b] = [0, 1];
  return () => {
    [a, b] = [b, a + b];
    return a;
  }
})();

nextFib();    // 1
nextFib();    // 1
nextFib();    // 2
nextFib();    // 3
nextFib();    // 5
nextFib();    // 8

Fun fact: the expression that we assigned to nextFib is called an IIFE (an acronym for immediately invoked function expression). Do you see why it is called that?

Generators

There’s another way to do that previous example. It’s a little weirder. Learn both ways.

function* fibGenerator() {
  let [a, b] = [0, 1];
  while (true) {
    [a, b] = [b, a + b];
    yield a;
  }
}

const fib = fibGenerator();
fib.next();    // { value: 1, done: false }
fib.next();    // { value: 1, done: false }
fib.next();    // { value: 2, done: false }
fib.next();    // { value: 3, done: false }
fib.next();    // { value: 5, done: false }
fib.next();    // { value: 8, done: false }

The generator starts out suspended. When you invoke next() on it, it runs until a yield then suspends until you call next() again.

Generators don’t have to have inifinite loops. You can have them end. Then they look good in for-loops. Example:

function* powerGenerator(base, limit) {
  let value = 1;
  while (value <= limit) {
    yield value;
    value *= base;
  }
}

const twos = powerGenerator(2, 5);
twos.next()           // {value: 1, done: false}
twos.next()           // {value: 2, done: false}
twos.next()           // {value: 4, done: false}
twos.next()           // {value: undefined, done: true}
twos.next()           // {value: undefined, done: true}

for (let t of powerGenerator(3, 100)) {
  console.log(t);
}
// prints 1 3 9 27 81

There’s more to all this, so see the MDN Docs on this topic.

Currying

Check out these two functions:

const f = (x, y) => x + y;    // Uncurried

const g = x => y => x + y;    // Curried

Okay, so we call the first one like f(5, 8) and the second one like g(5)(8), but is there any other difference? Well, the second one admits partial application:

const add5 = g(5);  // Applying the “first” argument now
add5(12);           // and the “second” argument later
add5(100);          // We can use the partially applied function over and over
Exercise: Write a “chainable” function that accepts one string per call, but when called without arguments, returns the words previously passed, in order, separated by a single space. For example say('Hello')('my')('name')('is')('Colette')() should return the string 'Hello my name is Colette'.

This

JavaScript has a really wild way to handle context.

The expression this evaluates to different things based on what the heck is going on at run time.

  1. If used in a top-level statement outside of any function, or at the top-level statement in a function called as a global function, it refers the global object.
    a = 3;
    this.a;                      // 3
    this.a = 2;
    a;                           // 2
    function f() {this.a = 10}
    f();
    a;                           // 10
    
  2. If used in a function produced through one of the methods apply, call, or bind, the value for this is the first argument of the method. Woah! (Note: there are other places in JavaScript where you can “pass in” a value for this, but these are the three main ones, perhaps.) This totally requires examples to understand:
    let a = {x: 1, y: 3}
    function f(c, s) {this.x = this.x * c + s;}
    
    f.apply(a, [3, 5]);
    a;                           // { x: 8, y: 3 }
    f.call(a, 4, 1);
    a;                           // { x: 33, y: 3 }
    f.bind(a)(3, 8);
    a;                           // { x: 107, y: 3 }
    
  3. If called via the syntax obj.f() where f is a NON-ARROW function property of obj, then this refers to obj, which we call the receiver, while the function is called a method.
    let x = {a: 1, b: function (x) {return x + this.a;}};
    x.b(2);                                    // 3
    
    // Wait we should use a more modern syntax
    let x = {a: 1, b(x) {return x + this.a;}};
    x.b(2);                                    // 3
    
    // The expression is evaluated dynamically, not statically
    let f = function (x) {return x + this.a;}
    let x = {a: 10, b: f};
    x.b(2);                                    // 12
    
    // Hey let's bundle up methods with our data!
    let p = {
      x: 0,
      y: 0,
      move(dx, dy) {this.x += dx; this.y += dy;},
      reflect() {this.x = -this.x; this.y = -this.y;}
    };
    p.move(5, 4);
    p.reflect();
    [p.x, p.y];      // [-5, -4]
    
  4. If used in a function invoked with the new operator, it refers to the object being created. Let’s get to this now.

Constructors

The previous example made a point object containing data fields and methods. If we want to make millions of point objects, each one of them would have copies of the move and reflect functions. If you care about saving memory, we should put those functions in a prototype. Interestingly, Eich, way back in 1995, created an operator, called new, that facilitates a pattern of creating objects with a shared prototype. Here’s how it works:

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
};
Point.prototype.move = function (dx, dy) {this.x += dx; this.y += dy;}
Point.prototype.reflect = function () {this.x = -this.x; this.y = -this.y;}
Point.prototype.toString = function () {return `(${this.x},${this.y})`;}

let p = new Point(3, 7);  // THE new IS CRUCIAL!

When you call the function with new, the function you call behaves as a constructor which means:

This works because of something interesting: every function you create has two properties: length, which holds the number of parameters for the function, and prototype, which is an object with a constructor property, whose value is the function itself.

jspoints.png

Exercise: We know that the prototype chain is searched when we try to read a property from an object that doesn’t have the property. But what happens if you try to write a property that doesn’t exist in an object, but does exist in an object higher in the prototype chain?

Class Syntax

There is a keyword class which you can use to build exactly the same structure we built above.

class Point {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
  move(dx, dy) {this.x += dx; this.y += dy;}
  reflect() {this.x = -this.x; this.y = -this.y;}
  toString() {return `(${this.x},${this.y})`;}
};

let p = new Point(3, 7);  // THE new IS CRUCIAL!

This code creates a function called Point whose body is the constructor, and it assigns those three functions to Point.prototype. Please keep in mind that using the class keyword is nothing more than syntactic sugar for functions and prototypes. There are no such thing as classes in JavaScript. Only functions.

Exercise: Try out this code. Evaluate typeof Point. You got "function", right?

Prototype Chains

The class syntax has a little feature to automatically make prototype chains, which is a way of doing inheritance. Here’s an example:

animals.js
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} says ${this.sound()}`; }
}

class Cow extends Animal {
  sound() { return 'moooo'; }
}

class Horse extends Animal {
  sound() { return 'neigh'; }
}

class Sheep extends Animal {
  sound() { return 'baaaa'; }
}

const animals = [new Horse('CJ'), new Cow('Bessie'), new Sheep('Little Lamb')];
animals.forEach(a => console.log(a.speak()));
console.log(animals[Math.floor(Math.random() * animals.length)].sound());
Exercise: Draw a picture of this object family.
These so-called “deep class hierarchies” are frowned upon by some folks in the JavaScript community. But go ahead and use them if you know what you are doing and you have one of those rare cases where it makes sense for your application.

The Big Deal about Arrow vs. Non-Arrow Functions

Did you catch, in our explanation of the this expression, that this is bound to the receiver only for non-arrow functions? That’s a big deal, since it means methods should never use arrows:

const counter = {
  val: 0,
  inc() { this.val++; },       // This does what you want
  dec: () => { this.val--; }   // NOOOOOOO THIS WON'T WORK
};

It’s a big deal, too, for functions nested within methods. You should use arrows for these, so that the this doesn’t get hijacked:

const counter = {
  val: 10,
  countDown() {
    if (this.val > 0) setTimeout(() => {console.log(--this.val); this.countDown();}, 1000);
  }
};
Exercise: What would happen if you replaced the first argument of setTimeout with function () {console.log(--this.val); this.countDown();}?
Remember, arrow functions don’t get this mapped to their receiver.

When using methods, i.e., functions inside an object that need to refer to the object, you want the function syntax. You can use the arrows everywhere else.

Crockford doesn’t use this, new, or prototypes

Douglas Crockford suggests that you do things like this:

classless.js
function circle(spec) {
  let { radius } = spec;
  const area = () => Math.PI * radius * radius;
  const expand = (factor) => { radius *= factor; };
  const toString = () => `Circle with radius ${radius}`;
  return Object.freeze({ area, expand, toString });
}

function rectangle(spec) {
  let { width, height } = spec;
  const area = () => width * height;
  const transpose = () => { [width, height] = [height, width]; };
  const toString = () => `Rectangle ${width} x ${height}`;
  return Object.freeze({ area, transpose, toString });
}

const c = circle({ radius: 10 });
const r = rectangle({ height: 8, width: 20 });
console.log(c.toString());
c.expand(3);
console.log(c.toString());
console.log(c.area());
console.log(r.toString());
r.transpose();
console.log(r.toString());

Kind of nice, but remember each object has its own copy of each of its methods, so don’t go using this technique when you are creating millions of objects with the same set of methods.

And here’s a neat little article on avoiding this (by someone other than Crockford). It explains how using this just adds a lot of cognitive load that we really don’t need, and how to avoid issues by using only pure functions and objects.

Regular Expressions

JavaScript has special syntactic sugar for regular expressions, for example:

JavaScript’s regular expression language is pretty standard:

Two ways to create:

re = /a+bc/;
re = new RegExp("a+bc");

JavaScript doesn’t have as rich a regex language as most other languages, but it has enough for most cases. The complete reference is here. In practice, you will want to grab a third-party module like xregexp.

Methods:

regex.exec(str)
If there’s a match, returns an array of match info. If no match, returns null.
regex.test(str)
Simply returns true if there’s a match and false otherwise.
str.match(regex)
Without the g modifier, same as regex.exec(str). With the g modifier, returns an array of all matches.
str.search(regex)
Returns the index of the beginning of the match, or -1 if no match.
str.replace(regex, newTextOrFunction)
Replaces the matched part of the string with new text.
str.split(strOrRegex)
Returns an array of substrings split by the regex separator.

Important:

Asynchronous Programming

JavaScript is generally used in aynchronous contexts. Learn about callbacks, promises, async functions, and the await construct in these notes.

Metaprogramming

There is a lot of disagreement about what actually constitutes metaprogramming, but for these notes let’s call it operating on program constructs as opposed to operating on application-level objects. For example, programming would involve asking “how old is this person?” or “what is the shortest path from Los Angeles to Madrid?” and metaprogramming might ask “what are the properties of this object and which are writable?”, “change the prototype this object to something else,” or “manufacture some new code at runtime from a string and execute it.”

Object Introspection and Self-Modification

You can inquire about, access, and modify an object’s prototype:

let basicCircle = { x: 0, y: 0, r: 1 }
let c = Object.create(basicCircle)
c.x = 2
c.color = 'blue'
Object.getPrototypeOf(c)                          // basicCircle
Object.getPrototypeOf(basicCircle)                // Object.prototype
Object.prototype.isPrototypeOf(basicCircle)       // true
basicCircle.isPrototypeOf(c)                      // true
let anotherCircle = { x: 10, y: 3 }
Object.setPrototypeOf(anotherCirce, basicCircle)  // but this is slow

You can inquire about, access, and modify an object’s properties:

Object.keys(c)                             // [ 'x', 'color' ]
Object.getOwnPropertyNames(c)              // [ 'x', 'color' ]
let a = []; for (let p in c) a.push(p); a  // [ 'x', 'color', 'y', 'r' ]
c.hasOwnProperty('color')                  // true
c.hasOwnProperty('y')                      // false

You can inquire about and change the way properties are allowed to be used:

Object.getOwnPropertyDescriptor(c, 'x')          // { value: 2,
                                                 //   writable: true,
                                                 //   enumerable: true,
                                                 //   configurable: true }
c.x = 10
c.x                                              // 10
Object.defineProperty(c, 'x', {writable: false})
Object.getOwnPropertyDescriptor(c, 'x')          // { value: 10,
                                                 //   writable: false,
                                                 //   enumerable: true,
                                                 //   configurable: true }
c.x = 500                                        // no error, but ...
c.x                                              // 10

Those things that are in the property descriptors are called attributes. The initial attribute values depend on how the object is created:

// Object literals have free, open, changeable properties
a = { x: 1, y: 2 }
Object.getOwnPropertyDescriptor(a, 'x')   // { value: 1,
                                          //   writable: true,
                                          //   enumerable: true,
                                          //   configurable: true }

// Object.create makes rather closed and locked-down properties
b = Object.create(Object.prototype, {x: { value: 1 }, y: { value: 2 }})
Object.keys(b)                            // []
Object.getOwnPropertyNames(b)             // [ 'x', 'y' ]
Object.getOwnPropertyDescriptor(b, 'x')   // { value: 1,
                                          //   writable: false,
                                          //   enumerable: false,
                                          //   configurable: false }

Important: A property can have either a data descriptor OR an access descriptor.

What do these mean?

Properties have data descriptors unless you define them with the get or set keywords. The usual example:

const c = {
  radius: 1,
  get area() { return Math.PI * this.radius * this.radius; },
  set area(a) { this.radius = Math.sqrt(a / Math.PI); },
};

console.log(c);            // prints { radius: 1, area: [Getter/Setter] }
c.radius = 2;              // It's writable
console.log(c.area);       // prints 4π (12.566370614359172)
c.area = 100 * Math.PI;    // calls the setter
console.log(c.radius);     // prints 10

See the MDN page for defineProperty for great examples.

You can even change how an object itself is allowed to be modified:

You can inquire which objects exist in your program. Just get a handle on the global object and list its properties.

Eval

Calling eval(s) evaluates the string s as JavaScript code.

Aside from a tiny number of legitimate, and very advanced, use cases, this function is a major security risk and never almost never be used. Before you try to use it, look for an alternative.

Read more about eval at Axel Rauschmayer’s blog.

Proxies

Read a fabulous overview by Axel Rauschmayer.

Modules

Roughly, a module is a file containing code that can be exported. Scripts (and other modules) can import code from a module and use it.

For an excellent overview, read Rauschmayer’s book chapter.

JavaScript’s official module system was created long after people created their own. In fact, the module system used by npm (the largest module ecosystem for any language anywhere on the planet) uses a completely incompatible module system! If you are using Node, you will define and use modules like this:

stats.js
function sum(a) {
  return a.reduce((x, y) => x + y, 0);
}

function mean(a) {
  return sum(a) / a.length;
}

function variance(a) {
  const m = mean(a);
  return sum(a.map(x => (x - m) ** 2)) / a.length;
}

module.exports = { mean, variance };
main.js
const { mean, variance } = require('./stats');

const scores = [35, 99, 22, 57, 98, 75, 69, 88];
console.log(mean(scores), variance(scores));
$ node main.js
67.875 704.609375

Some Reference Material

Statements

A program is a sequence of statements and function declarations. Function declarations begin with function, function*, async function, async function*, or class. The statements are:

Keywords

Keywords are those words that may not be used as identifiers:

Future Keywords may be used in the future as keywords:

The following are essentially reserved due to static semantic restrictions in some contexts:

Operators

From highest to lowest precedence:

Operators Associativity Description
.
[]
new
without arguments
L member (rhs cannot start with a digit)
member
create instance
()
new
with arguments
N/A call
create instance
++
--
N/A postfix increment
postfix decrement
!
~
-
+
++
--
typeof
void
delete
R logical not
bitwise not
unary negation
unary plus
prefix increment
prefix decrement
type name
evaluate and return undefined
delete
** L exponentiation
*
/
%
L multiply
divide
remainder
+
-
L add
subtract
<<
>>
>>>
L left shift
arithmetic right shift (sign fill)
logical right shift (zero fill)
<
<=
>
>=
in
instanceof
L less than
less than or equal
greater than
greater than or equal
has property
has type
==
!=
===
!==
L similar to
not ==
equals (and same type)
not ===
& L bitwise and
^ L bitwise xor
| L bitwise or
&& L short-circuit logical and
|| L short-circuit logical or
?: R conditional
=
+= -=
*= /= %=
<<= >>= >>>=
&= ^= |=
R assignment
yield
yield*
N/A return from generator
delegate to another generator
... N/A spread
, L comma

Good to know:

Built-in Objects

JavaScript, the language, tracks the ECMAScript Scripting Language Specification, so all of the standard objects in the ECMAScript specification are available to your JavaScript programs. Here are those objects, with a representative set of their properties, as of ES9 (The 9th edition of the ECMAScript Specification, a.k.a. ES2018):

NamePrototypeProperties
The global
object
(unnamed)
Implemen-
tation
dependent
Infinity NaN undefined eval() isFinite() isNaN() parseFloat() parseInt() decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() Math JSON Reflect Atomics Object() Function() Boolean() Number() String() Symbol() Array() Float32Array() Float64Array() Int8Array() Int16Array() Int32Array() Uint8Array() Uint8ClampedArray() Uint16Array() Uint32Array() ArrayBuffer() SharedArrayBuffer() DataView() Date() RegExp() Set() WeakSet() Map() WeakMap() Proxy() Promise() Error() SyntaxError() RangeError() TypeError() ReferenceError() URIError() EvalError()
ObjectFunction.
prototype
prototype create() assign() is() getPrototypeOf() setPrototypeOf() defineProperties() defineProperty() getOwnPropertyDescriptor() getOwnPropertyDescriptors() getOwnPropertyNames() getOwnPropertySymbols() keys() values() entries() preventExtensions() isExtensible() seal() isSealed() freeze() isFrozen()
Object.prototypenullconstructor toString() toLocaleString() valueOf() hasOwnProperty() isPrototypeOf() propertyIsEnumerable()
MathObject.
prototype
E LN10 LN2 LOG2E LOG10E PI SQRT1_2 SQRT2 abs() acos() acosh() asin() asinh() atan() atanh() atan2() cbrt() ceil() clz32() cos() cosh() exp() expm1() floor() fround() hypot() imul() log() log1p() log10() log2() max() min() pow() random() round() sign() sin() sinh() sqrt() tan() tanh() trunc()
JSONObject.
prototype
parse() stringify()
ReflectObject.
prototype
getPrototypeOf() setPrototypeOf() construct() ownKeys() has() defineProperty() getOwnPropertyDescriptor() get() set() apply() deleteProperty() isExtensible() preventExtensions()
AtomicsObject.
prototype
add() and() compareExchange() exchange() isLockFree() load() or() store() sub() wait() wake() xor()
FunctionFunction.
prototype
length prototype
Function.prototypeObject.
prototype
constructor toString() apply() call() bind()
Function instancesFunction.
prototype
name length prototype
BooleanFunction.
prototype
prototype
Boolean.prototypeObject.
prototype
constructor toString() valueOf()
NumberFunction.
prototype
prototype NEGATIVE_INFINITY POSITIVE_INFINITY MIN_VALUE MAX_VALUE isFinite() EPSILON isInteger() MIN_SAFE_INTEGER MAX_SAFE_INTEGER isSafeInteger() NaN isNaN() parseInt() parseFloat()
Number.prototypeObject.
prototype
constructor toString() toLocaleString() valueOf() toFixed() toExponential() toPrecision()
StringFunction.
prototype
prototype fromCharCode() fromCodePoint() raw()
String.prototypeObject.
prototype
constructor charAt() charCodeAt() codePointAt() startsWith() endsWith() includes() indexOf() lastIndexOf() slice() substring() toLowerCase() toUpperCase() toLocaleLowerCase() toLocaleUpperCase() normalize() localeCompare() trim() concat() repeat() match() search() replace() split() padStart() padEnd() toString() valueOf()
String instancesString.
prototype
length
SymbolFunction.
prototype
prototype hasInstance isConcatSpreadable iterator asyncIterator match replace search species split() toPrimitive toStringTag unscopables for() keyFor()
Symbol.prototypeObject.
prototype
constructor toString() valueOf()
RegExpFunction.
prototype
prototype
RegExp.prototypeObject.
prototype
constructor flags global ignoreCase unicode multiline sticky dotAll source test() exec() toString()
RegExp instancesRegExp.
prototype
lastIndex
DateFunction.
prototype
prototype parse() UTC() now()
Date.prototypeObject.
prototype
constructor getDate() getTime() getFullYear() getMonth() getDay() getHours() getMinutes() getSeconds() getMilliseconds() getTimezoneOffset() getUTCDate() getUTCFullYear() getUTCMonth() getUTCDay() getUTCHours() getUTCMinutes() getUTCSeconds() getUTCMilliseconds() setDate() setTime() setFullYear() setMonth() setHours() setMinutes() setSeconds() setMilliseconds() setUTCDate() setUTCFullYear() setUTCMonth() setUTCHours() setUTCMinutes() setUTCSeconds() setUTCMilliseconds() toString() toISOString() toUTCString() toDateString() toTimeString() toLocaleString() toLocaleDateString() toLocaleTimeString() toJSON() valueOf()
ArrayFunction.
prototype
prototype isArray() of() from()
Array.prototypeObject.
prototype
constructor keys() values() entries() includes() find() findIndex() indexOf() lastIndexOf() slice() every() some() forEach() map() filter() reduce() reduceRight() pop() push() shift() unshift() copyWithin() join() concat() fill() sort() reverse() splice() toLocaleString() toString()
Array instancesArray.
prototype
length
Int8Array
Uint8Array
Uint8ClampedArray
Int16Array
Uint16Array
Int32Array
Uint32Array
Float32Array
Float64Array
Function.
prototype
prototype BYTES_PER_ELEMENT of() from()
Int8Array.prototype
Uint8Array.prototype
Uint8ClampedArray.prototype
Int16Array.prototype
Uint16Array.prototype
Int32Array.prototype
Uint32Array.prototype
Float32Array.prototype
Float64Array.prototype
Object.
prototype
constructor BYTES_PER_ELEMENT buffer byteLength byteOffset length keys() values() entries() includes() find() findIndex() indexOf() lastIndexOf() slice() every() some() forEach() map() filter() reduce() reduceRight() copyWithin() join() fill() sort() reverse() set() subarray() toLocaleString() toString()
SetFunction.
prototype
prototype
Set.prototypeObject.
prototype
constructor size has() keys() values() entries() add() clear() delete() forEach()
ArrayBufferFunction.
prototype
prototype isView
ArrayBuffer.prototypeObject.
prototype
constructor byteLength slice()
SharedArrayBufferFunction.
prototype
prototype
SharedArrayBuffer.prototypeObject.
prototype
constructor byteLength slice()
DataViewFunction.
prototype
prototype
DataView.prototypeObject.
prototype
constructor buffer byteLength byteOffset getInt8() setInt8() getInt16() setInt16() getInt32() setInt32() getUint8() setUint8() getUint16() setUint16() getUint32() setUint32() setFloat64() setFloat32() getFloat64() getFloat32()
WeakSetFunction.
prototype
prototype
WeakSet.prototypeObject.
prototype
constructor has() add() has() delete()
MapFunction.
prototype
prototype
Map.prototypeObject.
prototype
constructor size has() get() set() keys() values() entries() clear() delete() forEach()
WeakMapFunction.
prototype
prototype
WeakMap.prototypeObject.
prototype
constructor has() get() set() delete()
ProxyFunction.
prototype
revocable()
Proxy.prototypeObject.
prototype
PromiseFunction.
prototype
prototype all() race() resolve() reject()
Promise.prototypeObject.
prototype
constructor then() catch()
ErrorFunction.
prototype
prototype
Error.prototypeObject.
prototype
constructor name message toString()
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
Function.
prototype
prototype
EvalError.prototype
RangeError.prototype
ReferenceError.prototype
SyntaxError.prototype
TypeError.prototype
URIError.prototype
Error.
prototype
constructor name message

JavaScript in Practice

There’s JavaScript the language, and then there’s all the culture around it, which is pretty much inseparable. To use JavaScript in real life, this cultural awareness is crucial.

Versions of JavaScript

What people call the different “versions” of JavaScript vary a little bit, but it’s a good bet people agree on these versions:

Check Kangax's Compatibility Tables to see which implementations support which features of the different versions. Note the radical differences!

Host Objects

JavaScript programs run inside a host environment, which exposes its own global objects. Most web browser environments provide:

On the web, all of the elements (including images and video), attributes, and other pieces of the a web page are part of the Document Object Model or DOM. These are all visible to JavaScript, so most JavaScript programs essentially do graphics by manipulating the DOM (as in the BMI script at the beginning of these notes). Unfortunately there are quite a few differences between browsers when it comes to the DOM.

Graphics

I have a separate page of notes for this.

Server Side

Node.js is the dominant, if not the only choice here. Understanding and using and mastering Node is a topic all in itself! It’s an system built on top of the V8 JavaScript Engine. It provides its own global variables, including:

console    setTimeout       Buffer         escape
process    clearTimeout     ArrayBuffer    unescape
module     setInterval      DataView
require    clearInterval    Intl
global     setImmediate
root       clearImmediate

And it comes with a number of built-in modules, including: assert, buffer, child_process, cluster, crypto, dgram, dns, domain, events, fs, http, https, net, os, path, punycode, querystring, readline, repl, stream, string_decoder, tls, tty, url, util, v8, vm, and zlib.

And it hosts an ecosystem of packages, called npm, the largest collection of open source packages in the world (by far!).

JavaScript Libraries

Most serious JavaScript is done with third-party libraries that are not only full of functionality, like special effects (fading, animation, etc.) but that wonderfully hide all the cross-browser differences from the programmer. Then there are the frameworks, gazillions of them.

Probably the major JavaScript libraries and frameworks are:

Unit Testing

I have a separate page of notes for this.

Some History

Interested in some history?

By the way, JavaScript is not like the other language with a similar name.

Wat

JavaScript is awesome, for the most part, but sometimes you need to laugh at its faults. Watch Gary Bernhardt’s famous Wat talk.