Today's focus is on language features and semantics. We will not really touch on the standard library.
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.
Mozilla Developer Network (MDN)
...nothing else, really?
ECMA-262 6.0 specification if you want to dive in
You can use the TypeScript playground set to JavaScript mode...
or just use your browser console.
console.log("Hello world!");
1; // number
1.2; // also a number
'string'; "string";
true; false; // booleans
undefined;
null;
/* multiline
comments */
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
}
// 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;
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
Falsy values are: false
, 0
, -0
, ""
, null
, undefined
, NaN
All other values are truthy.
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
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"
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
let x = 0;
x += 1; // x === 1
x *= 2; // x === 2
x /= 2; // x === 1
x -= 1; // x === 0
x ||= false; // x === false
// etc.
let x = 1;
`x is: ${x}`; // "x is: 1"
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
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 (true) {
console.log("True");
} else if (1 === 1) {
console.log("True too");
} else {
console.log("False");
}
if (true)
console.log("This works too");
aka ternary operator
true ? "true!" : "hmm..."; // "true!"
false ? 0
: 1 ? "true 2"
: true ? "true 3"
: "all false"; // ?
let i = 0;
while (i < 10) {
console.log(i++);
}
for (let i = 0; i < 10; ++i) {
console.log(i);
}
const o = {a: 1, b: 2};
'a' in o // true
'b' in o // true
'asdajd' in o // false
const array = ["hello", "world", 3, 4, 5];
for (const key in array) {
const v = array[key];
console.log(`${key} is ${v}`);
}
const array = ["hello", "world", 3, 4, 5];
for (const v of array) {
console.log(v);
}
const array = ["hello", "world", 3, 4, 5];
for (const v of array) {
if (v === "world")
continue;
if (v === 4)
break;
console.log(v);
}
switch ("abc") {
case 123:
// do stuff
break;
case "abc":
// do stuff
break;
default:
// do stuff
break;
}
const a1 = [1, 2, 3];
const a2 = [a1, ...a1, ...a1];
const o1 = {a: 1, b: 2}
const o2 = {o: o1, ...o1};
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]
const [a, b] = [10, 20];
a; // 10
b; // 20
let {c, d} = {c: 1, d: 2};
c; // 1
d; // 2
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);
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");
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}!`);
};
function five() {
return 5;
}
const five2 = () => 5;
function add(a, b) {
return a + b;
}
const add2 = (a, b) => a + b;
function f(a, ...b) {
console.log(a, b);
}
const a = [1, 2, 3, 4, 5];
f(...a);
f(0, ...a);
f(0, a);
function f([a, b], {c, d}) {
console.log(a, b, c, d);
}
f([1, 2], {c: 3, d: 4});
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);
}
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]}`);
}
}
Returns RHS if LHS is null or undefined
0 ?? 1; // 0
null ?? 1; // 1
undefined ?? 1; // 1
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
undefined, boolean, number, string, function, object, symbol
typeof undefined // 'undefined'
typeof false // 'boolean'
typeof 1 // 'number'
typeof "" // 'string'
typeof (() => false) // 'function'
typeof ({}) // 'object'
We can make objects already:
const dog = {
name: "Buster",
age: 3,
bark: function() {
console.log(`${this.name} barks`);
}
};
dog.bark();
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();
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();
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
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
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();
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();
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
class Animal {}
class Dog extends Animal {}
const a = new Animal();
const d = new Dog();
typeof a; // 'object'
typeof d; // 'object'
Oh no!
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
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!
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
function doWork() {
throw new Error("Error!");
}
try {
console.log("Doing work...");
doWork();
console.log("Done work!");
} catch (error) {
console.error("Got error", error);
}
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; }
}
setTimeout
setTimeout(() => {
console.log("Hello!");
}, 1000);
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!
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.");
new Promise(resolve => {
console.log("Promise run!");
setTimeout(() => {
resolve("Some work was done");
}, 1000);
}).then(result => {
console.log(result);
});
console.log("Outside the promise.");
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.");
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!");
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!");
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!");
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();
Read MDN's re-introduction to JavaScript
Try out Node.js
Join our HTML/CSS and React workshops in the next two weeks!