Hackerschool

Introduction to JavaScript

Overview

  • Introduction
  • Basics
  • OOP and classes
  • Event loop, promises, async/await

Today's focus is on language features and semantics. We will not really touch on the standard library.

JavaScript?

Created by Brendan Eich in 1995 to add interactivity to web pages in Netscape Navigator

Now one of the most popular programming languages in use.

Resources

Mozilla Developer Network (MDN)

...nothing else, really?

ECMA-262 6.0 specification if you want to dive in

Running JavaScript

You can use the TypeScript playground set to JavaScript mode...

or just use your browser console.

Basics

Hello world

console.log("Hello world!");

Expressions

1; // number
1.2; // also a number
'string'; "string";
true; false; // booleans
undefined;
null;
/* multiline
   comments */

Declarations (let, const)

const x = 1;
x = 2; // ERROR
let y = 1;
y = 2; // OK

{
  const x = 2;
  // this x shadows original x
  let y = 1;
  // this y also shadows original y
}

Basic operations

// arithmetic: all floats!
1 + 1; 1 - 1; 1 * 2; 1 / 2; 5 % 2;
2 ** 2;

// bitwise operators
2 & 1; 2 | 1; 2 ^ 1;
2 << 2; 2 >> 2; -2 >>> 2;

// comparison operators
1 > 2; 1 < 2;
1 >= 2; 1 <= 2;
1 == 2; 1 != 2; // avoid!
1 === 2; 1 !== 2;

In/decrement

let x = 0;
x++; // returns 0, x is now 1
++x; // returns 2, x is now 2
x--; // returns 2, x is now 1
--x; // returns 0, x is now 0

Truthy and falsy

Falsy values are: false, 0, -0, "", null, undefined, NaN

All other values are truthy.

Boolean operations (!)

Returns true if the operand is falsy, and false if the operand is truthy.

!false; // true
!true; // false
!""; // true
!0; // true
!"hello"; // false
!2; // false

Boolean operations (&&)

If the first value is falsy, returns it, else returns the second value. If the second value is not returned, it is not evaluated. (Short-circuiting)

false && true; // false
"" && true; // ""
true && true; // true
true && 0; // 0
true && "hello"; // "hello"

Boolean operations (||)

If the first value is truthy, returns it, else returns the second value. If the second value is not returned, it is not evaluated. (Short-circuiting)

false || true; // true
"" || "hello"; // "hello"
true || true; // true
true || 0; // true
true || "hello"; // true

Assignment operators

let x = 0;
x += 1; // x === 1
x *= 2; // x === 2
x /= 2; // x === 1
x -= 1; // x === 0
x ||= false; // x === false
// etc.

Template literals

let x = 1;
`x is: ${x}`; // "x is: 1"

Arrays

const a = ["hello"]; // array of 1 item
a.length // 1
a.push("world");
a.length // 2
a[0] // "hello"
a[1] // "world"
a[2] // undefined

Objects

const x = { "a": 123, "b": "word",
            "life is good!": false };
x["a"] // 123
x.a    // 123
x["b"] // "word"
x.b    // "word"
x["whatever"]      // undefined
x.somethingElse    // undefined
x["life is good!"] // false
x.life is good!    // can't do this!
delete x["a"];

If statements

if (true) {
  console.log("True");
} else if (1 === 1) {
  console.log("True too");
} else {
  console.log("False");
}

if (true)
  console.log("This works too");

Conditional expressions

aka ternary operator

true ? "true!" : "hmm..."; // "true!"
false ? 0
      : 1 ? "true 2"
          : true ? "true 3"
                 : "all false"; // ?

While loop

let i = 0;
while (i < 10) {
  console.log(i++);
}

For loop

for (let i = 0; i < 10; ++i) {
  console.log(i);
}

In operator

const o = {a: 1, b: 2};
'a' in o // true
'b' in o // true
'asdajd' in o // false

For-in loop

const array = ["hello", "world", 3, 4, 5];
for (const key in array) {
  const v = array[key];
  console.log(`${key} is ${v}`);
}

For-of loop

const array = ["hello", "world", 3, 4, 5];
for (const v of array) {
  console.log(v);
}

break, continue

const array = ["hello", "world", 3, 4, 5];
for (const v of array) {
  if (v === "world")
    continue;
  if (v === 4)
    break;
  console.log(v);
}

switch

switch ("abc") {
  case 123:
    // do stuff
    break;
  case "abc":
    // do stuff
    break;
  default:
    // do stuff
    break;
}

Spread operator

const a1 = [1, 2, 3];
const a2 = [a1, ...a1, ...a1];
const o1 = {a: 1, b: 2}
const o2 = {o: o1, ...o1};

Destructuring

let a, b, rest;
[a, b] = [10, 20];
a; // 10
b; // 20
[a, b, ...rest] = [1, 2, 3, 4, 5];
a; /* 1 */ b; /* 2 */
rest; // [3, 4, 5]

Destructuring

const [a, b] = [10, 20];
a; // 10
b; // 20
let {c, d} = {c: 1, d: 2};
c; // 1
d; // 2

Functions

function greet(a) {
  console.log(`Hello, ${a}!`);
  return 5;
}
console.log(greet("world"));

Function arity is not checked:

function f(a, b, c, d, e) {
  console.log(a, b, c, d, e);
}
f();
f(1);
f(1, 2);
f(1, 2, 3);
f(1, 2, 3, 4);
f(1, 2, 3, 4, 5);
f(1, 2, 3, 4, 5, 6);

First-class functions

Functions are values:

function greet(a) {
  console.log(`Hello, ${a}!`);
}
function apply(f, x) {
  f(x);
  return f;
}
apply(greet, "world");
const v = greet;
apply(v, "world");
apply(apply(v, "world"), "world");

Arrow functions

function greet(a) {
  console.log(`Hello, ${a}!`);
}
const greet2 = (a) => {
  console.log(`Hello, ${a}!`);
};
const greet3 = a => {
  // parens optional for one param
  console.log(`Hello, ${a}!`);
};

Arrow functions

function five() {
  return 5;
}
const five2 = () => 5;
function add(a, b) {
  return a + b;
}
const add2 = (a, b) => a + b;

Spread in functions

function f(a, ...b) {
  console.log(a, b);
}
const a = [1, 2, 3, 4, 5];
f(...a);
f(0, ...a);
f(0, a);

Destructuring in functions

function f([a, b], {c, d}) {
  console.log(a, b, c, d);
}
f([1, 2], {c: 3, d: 4});

Exercise 0

Fizzbuzz.

For numbers 1 to 100, print "fizz" if the number is divisible by 3, "buzz" if by 5, "fizzbuzz" if by both, else just the number.

for (let i = 1; i <= 100; ++i) {
  if (i % 15 === 0)
    console.log("fizzbuzz");
  else if (i % 3 === 0)
    console.log("fizz");
  else if (i % 5 === 0)
    console.log("buzz");
  else
    console.log(i);
}

Exercise 1

Write a function that prints out its arguments like this:

fn(1, "hello", "bye")

Number of arguments: 3
Argument 0: 1
Argument 1: hello
Argument 2: bye
function fn(...args) {
  console.log(`Number of arguments: ${args.length}`);
  for (const i in args) {
    console.log(`Argument ${i}: ${args[i]}`);
  }
}

?? operator

Returns RHS if LHS is null or undefined

0 ?? 1; // 0
null ?? 1; // 1
undefined ?? 1; // 1

?. operator

const o = { a: { b: 1 } };
const p = {};
const q = undefined;
o?.a?.b; // 1
p?.a?.b; // undefined
q?.a?.b; // undefined
p.a.b;   // error
q.a.b;   // error

JavaScript types

undefined, boolean, number, string, function, object, symbol

typeof

typeof undefined // 'undefined'
typeof false // 'boolean'
typeof 1 // 'number'
typeof "" // 'string'
typeof (() => false) // 'function'
typeof ({}) // 'object'

OOP and classes

Objects

We can make objects already:

const dog = {
  name: "Buster",
  age: 3,
  bark: function() {
    console.log(`${this.name} barks`);
  }
};
dog.bark();

Objects

We could extract this into a function:

const makeDog = (name, age) => ({
  name, age,
  bark() {
    console.log(`${this.name} barks`);
  }
});
const dog = makeDog("Buster", 3);
dog.bark();

Classes

Instead of what we did, JavaScript has classes:

class Dog {
  name; age;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  bark() {
    console.log(`${this.name} barks`);
  }
}
const dog = new Dog("Buster", 3);
dog.bark();

Private fields

class Dog {
  name; age; #trueName;
  constructor(name, age) {
    this.name = name;
    this.age = age;
    this.#trueName = name + "too";
  }
  bark() {
    console.log(`${this.name} barks`);
  }
}
const dog = new Dog("Buster", 3);
dog.trueName; // undefined
dog.#trueName; // error

Private methods

class Dog {
  name; age;
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  #bark() {
    console.log(`${this.name} barks`);
  }
  publicBark() { this.#bark(); }
}
const dog = new Dog("Buster", 3);
dog.publicBark();
dog.bark(); // error
dog.#bark(); // error

Inheritance

class Animal {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  makeNoise() {
    console.log(`${this.name} makes noise`);
  }
}
class Dog extends Animal {
  constructor(name, age) {
    super(name, age); // must call superclass ctor
    // do other setup...
    // otherwise this constructor is entirely optional
  }
  makeNoise() { console.log(`${this.name} barks`); }
}
const dog = new Dog("Buster", 3);
dog.makeNoise();

Inheritance

Use super to specifically access superclass values:

class Animal {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  makeNoise() {
    console.log(`${this.name} makes noise`);
  }
}
class Dog extends Animal {
  makeNoise() {
    super.makeNoise();
    console.log(`${this.name} barks too!`);
  }
}
const dog = new Dog("Buster", 3);
dog.makeNoise();

Static properties

class Point {
  x; y;
  constructor(x, y) { this.x = x; this.y = y; }
  static whatever = "I ran out of ideas";
  static distance(a, b) {
    return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2);
  }
}
const a = new Point(0, 0);
const b = new Point(3, 4);
Point.distance(a, b); // 5
Point.whatever // "I ran out of ideas"
a.distance; // undefined
a.whatever; // undefined

Distinguishing classes

class Animal {}
class Dog extends Animal {}
const a = new Animal();
const d = new Dog();
typeof a; // 'object'
typeof d; // 'object'

Oh no!

instanceof

class Animal {}
class Dog extends Animal {}
const a = new Animal();
const d = new Dog();
a instanceof Animal; // true
a instanceof Dog; // false
d instanceof Animal; // true
d instanceof Dog; // true

Passing methods around

class Dog {
  name;
  constructor(name) { this.name = name; }
  bark() { console.log(`${this.name} barks`); }
}
const d = new Dog("Buster");
d.bark(); // ok
const b = d.bark;
b(); // breaks!

Binding this

class Dog {
  name;
  constructor(name) { this.name = name; }
  bark() { console.log(`${this.name} barks`); }
}
const d = new Dog("Buster");
d.bark(); // ok
const b = d.bark.bind(d);
b(); // ok

Try-catch

function doWork() {
  throw new Error("Error!");
}
try {
  console.log("Doing work...");
  doWork();
  console.log("Done work!");
} catch (error) {
  console.error("Got error", error);
}

Exercise 2

Make a Shape class, which has a name property, and area and perimeter methods that just throw Errors.

Make Rectangle and Circle subclasses that implement those methods.

You may need Math.PI.

class Shape {
  name; constructor(name) { this.name = name; }
  area() { throw new Error("Not implemented"); }
  perimeter() { throw new Error("Not implemented"); }
}
class Rectangle extends Shape {
  a; b;
  constructor(a, b) {
    super("Rectangle");
    this.a = a; this.b = b;
  }
  area() { return this.a * this.b; }
  perimeter() { return this.a * 2 + this.b * 2; }
}
class Circle extends Shape {
  r;
  constructor(r) {
    super("Circle");
    this.r = r;
  }
  area() { return Math.PI * this.r**2;}
  perimeter() { return 2 * Math.PI * this.r; }
}

Asynchronous programming

setTimeout

setTimeout(() => {
  console.log("Hello!");
}, 1000);

Event loop

The JavaScript event loop is essentially:

while (queue.waitForEvent()) {
  queue.processNextEvent();
}

How does setTimeout work?

After the timeout, the callback is simply added to the queue and processed!

Promises

A Promise is a way to model a value that will only be known in the future.

new Promise(() => {
  console.log("Promise run!");
});
console.log("Outside the promise.");

Then

new Promise(resolve => {
  console.log("Promise run!");
  setTimeout(() => {
    resolve("Some work was done");
  }, 1000);
}).then(result => {
  console.log(result);
});
console.log("Outside the promise.");

Handling errors

new Promise((resolve, reject) => {
  console.log("Promise run!");
  setTimeout(() => {
    reject("Ran into an error!");
  }, 1000);
}).then(result => {
  console.log(result);
}).catch(error => {
  console.error(error);
})
console.log("Outside the promise.");

fetch

We'll use fetch as a real-world example.

fetch("https://api.github.com/orgs/nushackers")
  .then(response => response.json())
  .then(data => {
    console.log(data.name);
  });
console.log("This comes first!");

Async/await

Writing chained .then()s is cumbersome!

function pause(ms) {
  return new Promise((resolve) =>
    { setTimeout(resolve, 1000); });
}
async function doIt() {
  console.log("Waiting...");
  await pause(1000);
  console.log("Waited!");
}
doIt();
console.log("After doIt!");

Error handling

function doWork() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Ran into an error!");
    }, 1000);
  });
}
async function getWork() {
  console.log("Getting work...");
  try {
    const result = await doWork(1000);
    console.log("Got result: " + result);
  } catch (error) {
    console.log("Got error: " + error);
  }
  console.log("Done!");
}
getWork();
console.log("After getWork!");

Exercise 3

Convert the fetch example from earlier into async/await.

async function getOrgName(name) {
  try {
    const response =
      await fetch("https://api.github.com/orgs/" + name);
    const data = await response.json();
    return data.name;
  } catch (error) {
    console.error(error);
  }
}
async function main() {
  const name = await getOrgName("nushackers");
  console.log("Got name: " + name);
}
main();

What next?

Read MDN's re-introduction to JavaScript

Try out Node.js

Join our HTML/CSS and React workshops in the next two weeks!

Thank you