An opinionated guide to JavaScript. This is my own private cheat sheet that I thought others might find useful as well.

Table of Contents


History of JavaScript

JavaScript was first created in 1995 by Brendan Eich over 10 days while at Netscape. Unusually JavaScript has no concept of input/output; it is designed to run as a scripting language in a host environment, typically the web browser but also elsewhere such as on the server with NodeJS.

Its syntax is similar to Java and C languages. While JavaScript supports object-oriented programming it is prototype-based and functions are first class, meaning they can be passed as arguments to other functions.

JavaScript is often called an interpreted language but it is best thought of conceptually as compiled. The JavaScript engine reads through the entire code first checking for errors and variable/function declarations. Then it will go through and execute the code, top-to-bottom, line by line, recursively descending into code blocks as needed. This JIT (just-in-time compilation) approach yields many performance wins and is also adopted by Python and Ruby interpreters.

In JavaScript variables do not have types; the value has a type.

Deep Thoughts

JavaScript is single threaded: synchronous functions do not return until the work is complete or failed. Threads allow multiple execution through memory space so multiple things happen at the same time. The problem with threads is: races, deadlocks, reliability, performance.

If you have two threads, you can’t reason about when/how code is executed. It is impossible to have application integrity when subject to race conditions.

The next programming language is not here yet but when it arrives will be rejected out of hand. We need a new systems language beyond C (1960s).


Definitions

A statement is a set of words/symbols that represent a single concept.

An expression is any reference to a variable(s) and value(s) combined with operators. Statements are made up of one or more expressions.

// statement
a = b * 2;

// expression
a = b * 2 + foo(c * 3);

Types

There are 7 data types in JavaScript: 6 primitives and Object.

  • Object (not a primitive)
  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol

JavaScript lets us work with primitives as if they were objects. Primitives as objects have nice features like properties and methods, but for performance primitives should be as fast/lightweight as possible.

JavaScript’s approach:

  • primitives are still primitive
  • we can access methods/properties on strings, numbers, booleans, and symbols -> when this happens a special “object wrapper” is created that provides the extra functionality and is then destroyed

Object Wrappers are different for each primitive type and called String, Number, Boolean, and Symbol. They provide different sets of methods.

So primitives are not objects. They cannot store data. All property/method operations are performed the help of temporary object wrappers.

Object

Objects are key-value pairs that permeate every part of JavaScript. Except for the six primitive data types, everything else in JavaScript is an object. They can also be thought of as a dynamic collection of properties.

Objects exist within brackets {...} with an optional list of properties. A property is a key-value pair where the key (property name) is a string and the value can be anything (including more objects, so we can build data structures of arbitrary complexity).

// 1. Literal syntax
let obj = new Object();

// 2. Object literal syntax
let obj = {};

let obj = {
  name: 'Apple',
  details: {
    color: 'red',
    quantity: 10
  }
};

Object attribute access can be chained together.

obj.details.color; // 'red'
obj['details']['quantity']; // 10

Can create an object prototype Dog and then an instance of it Fido.

function Dog(name, age) {
  this.name = name;
  this.age = age;
}

let myDog = new Dog('Fido', 8);
// Created a new dog object

The ‘for…in’ loop To iterate over all the keys in an object can use the special for...in loop.

let person = {
  name: 'Will',
  age: 37,
  isMale: true
};

for (let key in person) {
  alert(key); // name, age, isMale
  alert(person[key]); // 'Will', 37, true
}

Bracket vs Dot Notation

There are two ways to access an object’s properties:

  • Dot Notation is faster to write and clearer to read. Works with only strings.
  • Bracket Notation allows access to properties with:
    • strings (quotes needed)
    • weird characters (quotes needed)
    • variables
    • numbers
    • expressions
let obj = {
  'name': 'Fido',
  'age': 10,
  'special[]': 'special chars',
}

obj.name; // 'Fido'
obj.age; // 10
obj.special[]; // Syntax Error

obj['name']; // 'Fido'
obj['age']; // 10
obj['special[]']; // 'special chars'

function count(obj) {
  for (var i in obj) {
    console.log(obj[i]);
  }
}

let three = {'one': 1, 'two': 2, 'three': 3};
count(three);
// 1
// 2
// 3

Pass by Value

All arguments are passed by value in JavaScript. In practice objects “seem” like they are passed by reference but that is because the “value” is a reference to the object.

Primitives store the actual data they represent, so when a primitive is passed, a copy of the data is sent, resulting in two copies of the data in memory. Changes to one won’t affect the other.

var a = 10;
var b = a;
a; // 10
b; // 10
var a = 5;
a; // 5
b; // 10

When you assign an Object to a variable, the variable stores the memory location for where the object can be found, not the object itself. This is sometimes called “Pass by Reference” but it’s still “Pass by Value”, the value is an object reference.

var a = { one: 1 };
var b = a;
a.one; // 1
b.one; // 1
a.one = 5;
a.one; // 5
b.one; // 5

Delete

Can use operator delete though rare.

let person = {
  name: 'Will',
  age: 37
};

delete person.age;
person; // {name: 'Will}

Existence check

The best way to check if a property exists is with in. Accessing a non-existent property returns undefined so previously this was done to check but it is possible to store undefined as a property which breaks the test.

let person = {
  name: 'Will',
  age: 37,
  weird: undefined
};

alert('age' in person); // true, person.age exists
alert('blah' in person); // false, person.blah does not exist
alert(person.name === undefined); // false, person.name exists
alert(person.noSuchProp === undefined); // true, person.noSuchProp does not exist
alert(person.weird === undefined); // true, but person.weird DOES exist!

Nesting Objects

A nested object is a regular object. You just need a nested loop if you want to reach all properties in the nested objects.

var dogTypes = {
  GermanShepard: {
    color: "black and white"
  },
  Beagle: {
    color: "brown and white"
  },
  cheuwahwah: {
    color: "green and white"
  },
  poodle: {
    color: "purple and white"
  },
};

for (var key in dogTypes) {
  for (var key2 in dogTypes[key]) {
    console.log(key, key2, dogTypes[key][key2]);
  }
}
// GermanShepard color black and white
// Beagle color brown and white
// cheuwahwah color green and white
// poodle color purple and white

If there’s only one, known key in the nested object, then you don’t need a loop, but then you also don’t really need a nested object.

Boolean

The Boolean type results in either true or false. Any value can be converted to a boolean with the following two rules:

  1. false, 0, empty string "", NaN, null, and undefined are all false.
  2. Everything else is true.
Boolean(''); // false
Boolean(234); // true

null

null is a value which indicates a deliberate non-value.

undefined

A value of the type undefined which indicates a declared but not assigned/initialized value.

Number

Double precision 64-bit format IEEE 754 values, so no such thing as an integer, therefore must be careful with arithmetic.

0.1 + 0.2 == 0.30000000000000004;

In practice integer values are treated as 32-bit integers. Also a built-in Math object that provides advanced mathematical functions and constants:

Math.sin(3.5);
let circumference = 2 * Math.PI * r;

Can convert string to number using parseInt() but should always include base as optional second argument.

parseInt('123', 10); // 123
parseInt('010', 10); // 10

Also can use parseFloat() which always uses base 10 or + operator.

+'42'; // 42
parseFloat('010'); // 10

Also special values Infinity and -Infinity.

1 / 0; // Infinity
-1 / 0; // -Infinity
isFinite(1 / 0); // false
isFinite(-Infinity); // false
isFinite(NaN); // false

NaN

NaN (“Not a Number”) is a special number, not a number per se. It is the result of an undefined or erroneous operation (like diving by 0). It is also toxic: any arithmetic with NaN will output NaN.

NaN is not equal to anything, including NaN.

parseInt('hello', 10); // NaN
NaN + 5; // NaN
NaN === NaN; // false
NaN !== NaN; // true
isNaN(NaN); // true

String

Strings are Unicode characters which makes internationalization easier and are represented by 16-bit numbers. They are immutable. They have a special length property and can be used like objects too!

'hello'.length; // 5
'hello'.charAt(0); // 'h'
'hello, world'.replace('hello', 'goodbye'); // 'goodbye, world'
'hello'.toUpperCase(); // 'HELLO'

String methods do not modify this - they always return new strings.

String Methods

Not a complete list.

// charAt(pos) -> returns character at index 'pos'
'abc'.charAt(1); // 'b'

// concat() -> return concatenation
'ab'.concat('cd', 'ef'); // 'abcdef'

// endsWith(str, endPos=this.length) -> returns true/false
'foo'.endsWith('oo'); // true
'abc'.endsWith('ab', 2); // true
'abc'.endsWith('ab', 3); // false

// includes(str, startPos=0) -> return true/false
'abc'.includes('b'); // true
'abc'.includes('b', 2); // false
'abc'.includes('b', 1); // true

// indexOf(str, minIndex=0) -> returns lowest index str appears or -1
'abab'.indexOf('a'); // 0
'abab'.indexOf('a', 1); // 2
'abab'.indexOf('c'); // -1

// lastIndexOf(str, maxIndex=Infinity) -> returns highest index of str
'abca'.lastIndexOf('a', 2); // 0
'abca'.lastIndexOf('a'); // 3

// padEnd(len, fillString=' ') -> appends fillString until desired len
'#'.padEnd(2); // '#  '
'abc'.padEnd(2); // 'abc' ... already full
'#'.padEnd(5, 'abc'); // '#abca'

// padStart(len, fillString=' ') -> prepends fillString until desired len
'#'.padStart(2); // ' #'
'abc'.padStart(2); // 'abc'
'#'.padStart(5, 'abc'); // 'abca#'

// repeat(count=0) -> return string repeated count times
'*'.repeat(); // ''
'*'.repeat(3); // '***'

// slice(start=0, end=this.length) -> return substring from start to end. Can use negative indices where -1 means len-1
'abc'.slice(1, 3); // 'bc'
'abc'.slice(1); // 'bc'
'abc'.slice(-2); // 'bc'

// startsWith(str, startPos=0) -> returns true if str at startPos, else false
'.gitignore'.startsWith('.'); // true
'abc'.startsWith('bc', 1); // true

// toUpperCase() -> uppercase all strings
'-a2b-'.toUpperCase(); // '-A2B-'

// toLowerCase() -> lowercase all strings
'-A2B-'.toLowerCase(); // '-a2b-'

// trim() -> return new string with all leading/trailing whitespace removed
'\r\n# \t'.trim(); // '#'

Symbol

The Symbol value represents a unique identifier. They always have different values even if they have the same name.

Symbols are used as “hidden” object properties where we can covertly hide something in objects we need, but others should not see. Internally JavaScript uses many system symbols which are accessible as Symbol.* and listed in the Well-known symbols table.

Technically symbols are not 100% hidden since there is a built-in method Object.getOwnPropertySymbols(obj) that allows us to get all symbols. Also there is a method named Reflect.ownKeys(obj) that returns all keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. Someone who explicitly calls the aforementioned methods probably understands well what they are doing.


Built-in Objects

Date

Date stores the date, time and provides methods for date/time management. We can use it to store creation/modification times, or to measure time, or just to print out the current date.

We must first create a new Date object with new Date().

let now = new Date();
alert(now); // shows current date/time

// 0 means 01.01.1970 UTC+0
let Jan01_1970 = new Date(0);
alert(Jan01_1970);

The number of milliseconds since the beginning of 1970 is called a timestamp.

new Date(year, month, date, hours, minutes, seconds, ms);
// only first two arguments required

new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00
new Date(2011, 0, 1); // the same, hours etc are 0 by default

If we only want to measure a time difference we don’t need the Date object, there’s a special method Date.now() that returns the current timestamp. It is semantically equivalent to new Date().getTime(), but it doesn’t create an intermediate Date object. So it’s faster and doesn’t put pressure on garbage collection.

// these are equivalent
let start = Date.now(); // milliseconds count from 1 Jan 1970
let start2 = new Date().getTime(); // milliseconds count from 1 Jan 1970

Can also read a date from a string, format is YYYY-MM-DDTHHH:mm:ss.sssZ where:

  • YYYY-MM-DD is the date: year-month-day
  • The character 'T' is used as the delimiter
  • HH:mm:ss.sss is the time: hours, minutes, seconds, and milliseconds
  • The optional 'Z' denotes the time zone in the format +-hh:mm. A single letter Z that would mean UTC+0.

Can also do shorter variants like YYYY-MM-DD, YYYY-MM, or YYYY.

let ms = Date.parse('2017-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417  (timestamp)

let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
alert(date);

Math

Math is a built-in object that has properties and methods for mathematical constants and functions. Not a function object.

Unlike the other global objects, Math is not a constructor. All properties and methods of Math are static. You refer to the constant pi as Math.PI and you call the sine function as Math.sin(x), where x is the method’s argument. Constants are defined with the full precision of real numbers in JavaScript.

Properties

Math.E; // ~2.718, Euler's constant and the base of natural logarithms
Math.LN2; // ~0.693, natural log of 2
Math.LN10; // ~2.303, natural log of 10
Math.LOG2E; // ~1.443, base 2 log of E
Math.LOG10E; // ~0.434, base 10 log of E
Math.PI; // ~3.14159, ratio of circumference of a circle to its diameter
Math.SQRT1_2; // ~0.707, square root of 1/2, equiv to 1 over square root of 2
Math.SQRT2; // ~1.414, square root of 2

Methods

// many methods, listing only the big ones
Math.abs(x); // returns absolute value of number x
Math.min([x, y...]); // returns smallest of zero or more numbers
Math.max([x, y...]); // returns largest of zero or more numbers
Math.random(); // returns pseudo-random number between 0 and 1
Math.round(x); // returns the value of a number rounded to nearest integer

Extending the Math object

As with most built-in objects in JavaSCript, the Math object can be extended with custom properties and methods. You do not use prototype, instead directly extend Math.

Math.propName = propValue;
Math.methodName = methodRef;

Set

The Set object lets you store unique values of any type, either primitives or object references.

const set1 = new Set([1, 2, 3, 4, 5]);
set1.has(1); // true
set1.has(6); // false

const arr = new Set([1, 2, 3, 1, 2]);
arr; // Set {1, 2, 3}

Arrays

Arrays are a special type of object with the magic property length, which is always one more than the highest index in the array.

let a = new Array();
a[0] = 'dog';
a[1] = 'cat';
a.length; // 2

// array literal
let b = ['dog', 'cat'];
b.length; // 2

// beware length isn't the number of items in the array but one more than highest index
let c = ['dog', 'cat'];
c[100] = 'fox';
c.length; // 101

// query non-existent array get undefined in return
typeof c[90]; // undefined

Array looping

Can iterate over an array with for, for...of, or forEach().

for (var i=0; i<a.length; i++) {
  // do something with a[i]
}

for (const currentValue of a) {
  // do something with currentValue
}

['dog', 'cat', 'hen'].forEach(function(currentValue, index, array))

Could also use for...in loop but if new properties added to Array.prototype also be iterated over so this type of loop not recommended for arrays.

Array methods:

// concat() -> returns new array with items added on to it
[1, 2, 3].concat(4); // [1,2,3,4]

// copyWithin(num, start, end=this.length) -> copy over target with elements, handles overlaps
['a', 'b', 'c', 'd'].copyWithin(0, 2, 4); // ['c', 'd', 'c', 'd']
['a', 'b', 'c', 'd'].copyWithin(1, 2); // ['a', 'c', 'd', 'd']

// entries() -> returns an iterable over [index, element] pairs
Array.from(['a', 'b'].entries()); // [ [ 0, 'a' ], [ 1, 'b' ] ]

// every(callback: value, index, array) -> returns true if callback returns true for every element, stops as soon as sees false
[1, 2, 3].every(x => x > 0); // true
[1, -2, 3].every(x => x > 0); // false

// fill(value, start=0, end=this.length) -> assigns value to every index
[0, 1, 2].fill('a'); // ['a', 'a', 'a']
[0, 1, 2].fills(5, 0, 1); // [5, 1, 2]

// filter(callback: value, index, array) -> returns array with only elements for which callback is true
[1, -2, 3].filter(x => x > 0); // [1, 3]

// find(predicate, value, index) -> returns first element where predicate is true, if no matches returns undefined
[1, -2, 3].find(x => x < 0); // -2
[1, 2, 3].find(x => x < 0); // undefined

// findIndex(predicate, value, index) -> returns index of first element where predicate is true, if no matches returns -1
[1, -2, 3].findIndex(x => x < 0); // 1
[1, 2, 3].findIndex(x => x < 0); // -1

// forEach(callback: value, index, array) -> calls callback for each element
['a', 'b'].forEach((x, i) => console.log(x, i));
// 'a' 0
// 'b' 1

// from() -> creates a new Array instance from an array-like or iterable object
Array.from('foo')); // Array ['f', 'o', 'o']
Array.from([1, 2, 3], x => x + x)); // Array [2, 4, 6]

// includes(searchElement, fromIndex=0) -> return true if searchElement, otherwise false
[0, 1, 2].includes(1); // true
[0, 1, 2].includes(5); // false

// indexOf(searchElement, fromIndex=0) -> returns index of first element strictly equal to searchElement, else -1
['a', 'b', 'a'].indexOf('a'); // 0
['a', 'b', 'a'].indexOf('a', 1); // 2
['a', 'b', 'a'].indexOf('c'); // -1

// join(separator=',') -> returns new array with values delimited by param
['a', 'b', 'c'].join(); // 'a,b,c'
['a', 'b', 'c'].join('/'); // [1/2/3/4]

// keys() -> returns an iterable over the keys of an array
[...['a', 'b'].keys()]; // [0, 1]

// lastIndexOf(searchElement, fromIndex=this.length-1) -> returns index of last element strictly equal to searchElement, or else -1
['a', 'b', 'a'].lastIndexOf('a'); // 2
['a', 'b', 'a'].lastIndexOf('a', 1); // 0
['a', 'b', 'a'].lastIndexOf('c'); // -1

// map(callback: value, index, array) -> returns new array where every element is the result of callback being applied
[1, 2, 3].map(x => x * 2); // [2, 4, 6]
['a', 'b', 'c'].map((x, i) => i); // [0, 1, 2]

// pop() -> removes and returns the last item
const arr = ['a', 'b', 'c'];
arr.pop(); // 'c'
arr; // ['a', 'b']

// push() -> appends items to end of the array, return value is length after change
const arr = ['a', 'b'];
arr.push('c', 'd'); // 4
arr; // [ 'a', 'b', 'c', 'd' ]

// reduce(callback: state, element) -> callback computes next state given current state and element of array. Starts at index 0, if no firstState provided array element of index 0 is used
[1, 2, 3].reduce((state, x) => state + String(x), ''); // '123'
[1, 2, 3].reduce((state, x) => state + x, 0); // 6

// reduceRight(callback: state, element) -> like reduce() but goes backwards
[1, 2, 3]. reduceRight((state, x) = state + String(x), ''); // '321'

// reverse() -> reverses the array
[1, 2, 3].reverse(); // [3,2,1]
[1, 2, 3].reverse(); // [1,2,3]

// shift() -> removes and returns the first item
const arr = [1, 2, 3];
arr.shift(); // 1
arr; // [2, 3]

// slice(start=0, end=this.length) -> returns a sub-array
[1, 2, 3].slice(0, 1); // [2,3]
[1, 2, 3].slice(); // [3]

// some(callback: value, index, array) -> return true if callback returns true at least once, stops when true
[1, 2, 3].some(x => x < 0); // false
[1, -2, 3].some(x => x < 0); // true

// sort() -> takes an optional comparison function
[1, 5, 3].sort(); // [1,3,5]
a.sort((a, b) => b - a); // [3,2]
a.sort((a, b) => a - b); // [2,3]

// splice() -> modify an array by deleting a section and replacing it with more items
let months = ['Jan', 'March', 'April'];
months.splice(1, 0, 'Feb'); // insert at 1st index position
months; // ['Jan', 'Feb', 'March', 'April']
months.splice(3, 1, 'May'); // replace 1 element at 3rd index
months; // ['Jan', 'Feb', 'March', 'May']

// toString() -> returns string with toString() of each element separated by commas
a.toString(); // '1,2,3'

// unshift() -> prepends items to the start of an array
let x = [1, 2, 3];
x.unshift(5); // 4
x; // [5,1,2,3]

Functions

Functions are objects that do something. Can take 0 or more named parameters. If no return statement exists then a function returns undefined.

Parameters vs Arguments: Parameters are placeholders/variables but before call-time have no value, are undefined. Arguments have a value and are passed in. Order matters.

function HelloWorld() {
  return 'Hello, World';
}

HelloWorld(); // 'Hello, World'

function sayHi(name) {
  return 'Hi ' + name;
}

sayHi('Will'); // 'Hi Will'

function add(num1, num2) {
  return num1 + num2;
}

add(3, 4); // 7
add(3, 4, 5); // don't have to use/return all parameters

Declarations vs Expressions

There are two ways to create a function in JavaScript.

function funcDeclaration() {
  return 'A function declaration';
}

const funcExpression = function() {
  return 'A function expression';
};

Function declarations are hoisted to the top of the scope so they load and have access to local variables before any other scope has been run. Function expressions do not.

Because function expressions are not hoisted, they work better in three instances:

  • closures
  • arguments to other functions
  • Immediately Invoked Function Expressions (IIFE)

More here re functions…https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript

Arguments keyword

arguments is a special keyword that lives inside functions that takes value of arguments in array-like object. Powerful when don’t know in advance how many arguments to take

var add = function(a, b) {
  console.log(arguments); // logs [3,10,5]
  return a + b;
};
add(3, 10, 5); // 13

// another example
var add = function(a, b) {
  console.log(arguments); // logs [3, 10, 5]
  return a + b + arguments[2];
};

add(3, 10, 5); // 18

// in-practice example
var add = function(a, b) {
  results = 0;
  for (var i = 0; i < arguments.length; i++) {
    results += arguments[i];
  }
  return results;
};

add(3, 10, 5, 3, 4, 6, 100); // 13

Also functions are objects so can attach properties if need to.

var add = function(a, b) {
  return a + b;
};

add.example = 'testing 123!';

Constructors

  • constructors use Capitalization to signify, a function that returns an object
  • function below has property speak
function AnimalMaker(name) {
  return {
    speak: function() {
      console.log('my name is ', name);
    }
  };
}

var myAnimal = AnimalMaker('Cheetah'); // used constructor to create animal object
myAnimal.speak(); // 'my name is Cheetah'
myAnimal['speak'](); // 'my name is Cheetah'

Looping

forEach or map preferred over for

for...in iterates through all members of an object, including methods, so use Object.keys instead.

Higher-Order Functions

A function that accepts another function as an argument.

Possible in JavaScript because functions are first class meaning they can be passed as parameters, returned, assigned to variables. Common examples are map, reduce, filter or any callback function such as around DOM events where something is intended to be called after executing.

// normal function
let sum = function(x, y) {
  return x + y;
}
sum(1,2); // 3

// higher-order function
function makeSumN(x) {
  return function(y) {
    sum(x, y)
  }
}
let sum1 = makeSumN(1);
sum1(2); // 3

// higher-order function
function makeMoreThanSum(x) {
  return function(y, z) {
    sum(x(y), z)
  }
}
let sumSquareOfFirst = makeMoreThanSum(function(n) {
  return n * n;
});
sumSquareOfFirst(2, 1); // 5

Examples using map, filter, and reduce. The function passed in is a callback function.

var animals = [
  { name: 'Fido',   type: 'dog', age: 10 },
  { name: 'Fluffy', type: 'cat', age: 14 },
  { name: 'Ralph',  type: 'dog', age: 5 },
  { name: 'Waldo',  type: 'dog', age: 11 },
];

// map() example
var oldDogs = animals.filter((animal) => animal.age >= 10 && animal.type === 'dog');
oldDogs; // array w/ Fido and Waldo objects

// filter() example
var oldDogNames = animals
  .filter((animal) => animal.age >= 10 && animal.type === 'dog');
  .map((animal) => animal.name)
oldDogNames; // ['Fido', 'Waldo']

// reduce() example
var totalDogYears = animals
  .filter((x) => x.type === 'dog')
  .map((x) => x.age)
  .reduce((prev, curr) => prev + curr, 0)
totalDogYears; // 26, Fido's 10 + Ralph's 5 + Waldo's 11

Variables

Can declare a new variable with let, const, or var.

let is block-scoped; const is also block-scoped and for values who are not intended to change (though objects can be mutated); var is function-scoped and has no restrictions.

If you declare a variable without assigning it to any value, its type is undefined.

const Pi = 3.14; // variable Pi is set
Pi = 1; // error b/c can't change constant value

// object properties can be mutated
const person = {
  name: 'Will'
};

const person = 10; // SyntaxError: already declared
person.name = 'Ashley';
person; // {name: 'Ashley'}

let a;
let name = 'Will';

for (let i = 0; i < 5; i++) {
  // i is only visible here
}

// i is *not* visible out here

for (var x = 0; x < 5; x++) {
  // x is visible to the whole function
}

// x *is* visible out here

No built-in way to create an independent copy of an object in JavaScript. Could replicate structure of existing one, iterate over its properties and copy them on the primitive level.

let person = {
  name: 'Will',
  age: 37
};

let clone = {}; // new empty object

// copy all user properties
for (let key in person) {
  clone[key] = person[key];
}

clone.name = 'Ashley'; // clone is independent
person.name; // 'Will', still original object

Simpler way is to use Object.assign().

let person = {
  name: 'Will',
  age: 37
};

let clone = Object.assign({}, person);

But what if properties are objects and therefore passed by reference? We need a deep clone not a shallow clone. Easiest is to just use _.cloneDeep(obj) from Lodash rather than re-implement the proper Structured cloning algorithm.

Temporal Dead Zone

Related to block-scope of let and const. Must declare functions/variables before using them inside of block scope. Else ReferenceError.

function foo(bar) {
  if (bar) {
    console.log(baz); // ReferenceError
    let baz = bar;
  }
}

foo('bar');

lets don’t hoist same as “temporal dead zone.”


Operators

Numeric operators are +, -, *, /, and %. Values are assigned using = and can do compound assignment statements like += and -=. Can also use ++ or -- to increment/decrement. The + operator also does string concatenation.

1 + 2; // 3
'hello' + ' world'; // 'hello world'

If you add a string to a number (or other value), everything is converted into a string first.

'3' + 4 + 5; // '345'
3 + 4 + '5'; // '75'
10 + ''; // '10'

Comparisons can be made with <, >, <=, and >= for strings and numbers. Double-equals == performs type coercion if you give it different types, so use triple-equals === to avoid.

123 == '123'; // true
1 == true; // true
123 === '123'; // false
1 === true; // false

Also != and !== operators.

Bitwise…https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators


Control Structures

Conditional statements are supported by if and else. Also while and do-while loops.

// if/else
let name = 'cat';
if (name == 'dog') {
  return 'woof';
} else if (name == 'cat') {
  return 'meow';
} else {
  return 'Not a cat or dog;';
}

// while loops
while (true) {
  // infinite loop
}

// do-while
let input;
do {
  input = get_input();
} while (inputIsNotValid(input));

// for
for (let i = 0; i < 5; i++) {
  // executes 5 times
}

// for-of
for (let property in object) {
  // do something with object property
}

The && and // operators use short-circuit logic so only execute second operand if first passes.

The switch statement is useful for multiple branches.

switch (action) {
  case 'draw':
    drawIt();
    break;
  case 'eat':
    eatIt();
    break;
  default:
    doNothing();
}

If don’t add break statement execution will “fall through” to the next level. Rarely what you want–a common cause of errors.

switch (a) {
  case 1: // fallthrough
  case 2:
    eatIt();
    break;
  default:
    doNothing();
}

Regular Expressions

Regular expressions are a powerful way to do search and replace in strings. In JavaScript they are implemented using objects of the built-in RegExp class.

A regular expressions consists of patterns and optional flags. Together they create a regular expression object.

There are many, many ways to do regular expressions. Don’t try to memorize them all.

// long syntax
regexp = new RegExp('pattern', 'flags');

// short one with slashes
regexp = /pattern/; // no flags
regexp = /pattern/gmi; // with flags g, m, and i

Slashes "/" tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings.

To search inside a string we can use str.search(regexp).

let str = 'I love JavaScript';
let regexp = /love/;
alert(str.search(regexp)); // 2

The str.search method looks for the pattern /love/ and returns the position inside the string. This code is the same as a substr search.

let str = 'I love JavaScript';
let substr = 'love';
alert(str.search(substr)); // 2

Searching for /love/ is the same as searching for 'love'.

Normally can use short syntax /.../ but it does not allow variable insertion so must know the exact regexp at the time of writing the code. Can also use new RegExp to construct a pattern dynamically from a string.

let search = prompt('What you want to search?', 'love');
let regexp = new RegExp(search);

// find whatever the user wants
alert('I love JavaScript'.search(regexp));

Flags

Regular expressions may have flags that affect the search. There are 5 in JavaScript:

  • i search is case-insensitive
  • g search looks for all matches, without it only the first one
  • m multiline mode
  • u enables full unicode support
  • y sticky mode: find a match exactly at the position specified by the property regexp.lastIndex and only there
// The "i" flag
let str = 'I love JavaScript';
alert(str.search(/LOVE/)); // -1 (not found), case-sensitive by default
alert(str.search(/LOVE/i)); // 2

Methods of RegExp and String

To search for first match only:

  • find position of first match - str.search(reg)
  • check if there’s a match - regexp.text(str)
  • find match from given position - regexp.exec(str), set regexp.lastIndex to position

To search for all matches:

  • an array of matches - str.match(reg), the regexp with g flag
  • get all matches with full info about each - regexp.exec(str) with g flag in the loop

To search and replace:

  • replace with another string or function result - str.replace(reg, str|func)

To split the string:

  • str.split(str|reg)

Character Classes

  • \d - digits
  • \D - non-digits
  • \s - space symbols, tabs, newlines
  • \S - all but \s
  • \w - English letters, digits, underscore _
  • \W - all but \w
  • '.' - any character except a newline

Can escape special characters too.


Error Handling

The try...catch syntax allows us to handle runtime errors (valid JavaScript that runs).

try {
  // code...
} catch (err) {
  // error handling
}

try...catch works synchronously so it won’t catch setTimeout for example. Instead must use try...catch inside async callbacks.

try {
  setTimeout(function() {
    noSuchVariable; // script dies here
  }, 1000);
} catch(e) {
  alert("won't work");
}

// this works
setTimeout(function() {
  try {
    noSuchVariable; // try...catch handles the error!
  } catch(e) {
    alert('error is caught here!');
  }
}, 1000);

The err object is passed as an argument to catch. It has two main properties:

  • err.name - error name, ex ReferenceError
  • err.message - text message about error details

Can also use try...catch...finally.

try {
  ... try to execute the code ...
} catch(e) {
  ... handle errors ...
} finally {
  ... execute always ...
}

Core Concepts

Scope

Scope is where to look for things, specifically variables that we reference. Where does it exist and where was it declared?

JavaScript is a compiled language, but not the same as C++/Java where the binary compiled form is distributed. JavaScript is compiled every single time that it’s run!

Interpreted languages go top-to-bottom one line at a time. Example is Bash, when run line 3, have no idea what to expect on line 4. But in compiled languages like JavaScript the compiler does an initial pass through the code to compile and then at least one more pass to execute. So it has looked at line 4 before it starts to run line 3.

Compilers look for blocks of scope and recursively descend into them.

function foo(b) {
  // b only exists within function
  c = 42;
  a = a * 2;
  a = a + b;
  return a / 2;
}

var a = 10;
var b = foo(3); // this b different than b parameter
console.log(a); // 23
console.log(c); // ReferenceError

You can always reference variables above in scope but not below (one-way street); ex why can find a in code above. But reverse not true for c, can’t take variable inside function and access outside of it.

// another example
var foo = bar;

function bar() {
	var foo = bad;

	function baz(foo) {
		foo = bam;
		bam = yay;
	}
	baz();
}

bar();
foo; // ??? 'bar'
bam; // ??? 'yay'
baz(); // ??? ReferenceError: not defined, only exists in bar scope not global

with and eval are evil and can cheat scope. Don’t use.

Closures

A closure is created whenever a variable that is defined outside the current scope is accessed from within some inner scope. Or, the context of an inner function includes the scope of the outer function.

Douglas Crockford calls them “maybe the best feature ever put in a programming language.” JavaScript the first language to implement correctly. Instead of using a stack, uses heap/garbage collection so inner function persists.

function create() {
  let counter = 0;
  return {
    increment: function() {
      counter++;
    },

    print: function() {
      console.log(counter);
    }
  };
}
let c = create();
c.increment();
c.print(); // ==> 1

IIFE Pattern

Immediately Invoked Function Expression.

Useful approach to wrap/hide in new function scope. Better to used named iffes rather than typical anonymous pattern.

var foo = 'foo';

(function iife() {
  var foo = 'foo2';
  console.log(foo); // 'foo2'
})(); // 'foo2'

console.log(foo); // 'foo'

Module Pattern

Take anything, put it inside of function, invoke immediately, but not create global variables.

var singleton = (function () {
  var privateVariable;
  function privateFunction(x) {
    ...privateVariable...
  }
  return {
    firstMethod: function (a, b) {
      ...privateVariable...
    },
    secondMethod: function (c) {
      ...privateFunction()...
    }
  };
}());

Useful for…

this

The this keyword is a reference to the object of invocation. This object is necessary for the JavaScript engine to execute the code: function/variable declarations and local variables on the scope chain. It is also key to prototypal inheritance.

To determine the value of this in any function, look at how it was called. There are four ways in JavaScript and arrow functions have a 5th case.

  1. Default Binding (Function): global context (non-strict mode) or undefined (strict mode)
  2. Implicit Binding (Method): the object
  3. Explicit Binding (apply, call, bind): the argument
  4. New Binding (Constructor): the new object
  5. Arrow Function: enclosing execution context

Rule 1: Function form (default binding)

If a function is invoked directly the value is either:

  • undefined in strict mode
  • the global scope.
function myFunc() {
  console.log(this);
}

// non-strict mode
myFunc(); // window

// strict mode
myFunc(); // undefined

Rule 2: Method form (implicit binding)

A function can be invoked as a method on an object. This is the most common and important use of this.

function logThis() { // Function form
  console.log(this);
}

function myObj = { // Method form
  function logThis() {
    console.log(this);
  }
}

logThis(); // window
myObj.logThis(); // myObj

The same logThis function, invoked in two different ways, yields two different values of this! What matters is not the function itself but how it is invoked!

Rule 3: Explicitly Binding (Apply, Call, Bind)

Three ways to set the value of this explicitly.

  • Call invokes the function and lets you pass in arguments one by one
  • Apply invokes the function and lets you pass in arguments as an array
  • Bind returns a new function, lets you pass in arguments one by one or as an array

Use bind with callbacks and events when you want a function to be called later. Use call/apply when you want to invoke the function immediately: call if you know the number of arguments; apply if you do not.

Call/apply basically the same. Remember Call is “C” for comma separated; Apply is “A” for array. Bind returns a new function while call/apply execute the current function immediately.

const person = 'William';

function sayHi(greeting) {
  console.log(greeting + this.person);
}

sayHi(person);

// example of bind() in React event code

// call()

// apply()

Rule 4: new Constructor

The new keyword is used with constructor functions to create a new instance of an object. When new is used to invoke a function, this inside of the function refers to the new empty object.

// example of Dog constructor function
function Dog() {
  console.log(this);
}

var a = new Dog(); // Dog {}
// this refres to the new Dog object created, it is empty

// can also add properties to `this`
function Cat(name) {
  console.log(this);
  this.name = ;
  console.log(this);
}

var b = new Cat('Max'); // Cat {}, Cat {name: 'Max'}
// the `this` keyword is initially set to the new empty object, then we immediately add `name` property to it

Rule 5: Arrow Functions

Uniquely arrow functions do not create their own this variable when invoked.

Need a good example here…using anonymous functions? Callback like setInterval?

Prototypal Inheritance

In JavaScript functions are also objects, which means they have properties. Every object has a property called prototype which is itself another object.

Prototypes = objects inherit from objects, not classes.

function foo() { ... }

typeof foo.prototype; // 'object'

In a “classic” object-oriented programming language all data is copied over into a new instance from a constructor function. This creates a parent/child relationship. But JavaScript is different.

In JavaScript, when a new object is created from a constructor function (which should be Capitalized), its core functionality is defined by its prototype which creates a reference chain called the prototype chain.

In the example below, fido doesn’t actually have its own method bark().

function Dog() { ... }

Dog.prototype.bark = function() {
  console.log('woof!');
};

const fido = new Dog();

fido.bark(); // 'woof!'
fido.hasOwnProperty('bark'); // false

What actually happens when we execute fido.bark() is:

  1. The JavaScript engine looks for a property called bark on the fido object
  2. It doesn’t find one, so it looks “up the prototype chain” to fido’s parent, which is Dog.prototype.
  3. It finds Dog.prototype.bark and calls it with this bound to fido.

To repeat since this is really important:

There’s no such property fido.bark. It doesn’t exist. Instead, fido has access to the bark() method on Dog.prototype because it’s an instance of Dog. This is “invisible link” is commonly called the “prototype chain”.

An interesting and dangerous thing about protypes in JavaScript is you can modify/extend a class after you have defined it! JavaScript will look up the prototype when trying to access properties on an object, which means you can alter your classes at runtime.

For example (don’t ever do this):

const arr = [1, 2, 3, 4, 5];

Array.prototype.shuffle = function() {
  return this.sort(function() {
    return Math.round(Math.random() * 2) - 1;
  });
};

arr.shuffle(); // [3,1,4,5,2]

The variable arr was created before Array.prototype.shuffle existed. But because property lookups go up the prototype chain, our array got access to the new method anyway–because it existed on Array.prototype by the time we tried to actually use it. It’s like we went back in time and gave Array a new method.

Event Loop

JavaScript is designed to run as a scripting language in a host environment, typically the web browser where there is a JavaScript Engine (like Chrome’s V8), a collection of Web APIs like the DOM, a Callback Queue, and the Event Loop.

The JavaScript Engine takes (broadly) two passes over the code: the first to find variable/function declarations, the second to execute the code line by line using a call stack. JavaScript is thus single threaded so if one piece of code (like a call to an API) takes a looong time, all the other code must wait and is blocked.

In order to avoid blocking code JavaScript has asynchronous callback functions -> functions that are executed later. For example setTimeout which is provided as a Web API. Or onClick, onLoad, onDone, etc.

let firstFunction = () => console.log('First');

let secondFunction = function() {
  setTimeout(firstFunction, 5000);
  console.log('Second');
};

secondFunction();

// I'm second
// (5 secs later) I'm first

Within JavaScript engine order of execution is:

  • secondFunction() is invoked
  • setTimeout() is called -> browser puts its callback function firstFunction into a Callback Queue.

The JavaScript Engine constantly checks if call stack is empty. If it is, it checks the callback queue and moves any functions waiting to be invoked over to the call stack. This is the Event Loop.

Hoisting

Not an actual thing but a term used to describe a behavior in JavaScript which is that function/variable declarations are “hoisted” to the top of function scope. This is because the JavaScript engine makes a first pass to compile the code and look for declarations, then a second pass to execute the code.

a; // ReferenceError: a is not defined
b; // ReferenceError: b is not defined
var a = b;
var b = 2;
b; // 2
a; // undefined!!!

Technically functions hoist before variables.

foo(); // 'foo'

var foo = 2;

function foo() {
  console.log('bar');
}

function foo() {
  console.log('foo');
}

foo() is called even though function declarations lower in code, first foo() is hoisted and then later foo() is hoisted on top of it. But var foo line is ignored because already declared.

Async

Asynchronous functions return immediately. Success/failure determined in the future.

Event loop pros:

  • no races or deadlocks
  • only one stack
  • low overhead
  • resilient: if a turn fails, the program can continue

Cons:

  • programs must never block
  • turns must finish quickly
  • programs are inside out

Callbacks

A callback argument is executed after an initial function has finished.

Promises

A promise is a special JavaScript object that links producing/consuming code together. It allows us to code things in natural order. First run the function, .then write what to do with the result, so can call .then as many times as we want later.

// basic promise syntax
let promise = new Promise(function(resolve, reject) {
  // executor (the producing code)
});

The function passed to new Promise is called executor. When the promise is created, it’s called automatically. It contains the producing code, that should eventually finish with a result.

.then/catch(handler) returns a new promise that changes depending on what the handler does:

  1. if returns a value promise is resolved and closest handler (first argument of .then) is called with that value
  2. if throws an error, new promise is rejected, and closest handler (second argument of .then or .catch) is called with it
  3. if it returns a promise, then JavaScript waits until it settles and then acts on its outcome the same way
// make a network request to url and return a promise
// promise resolves with a response object
fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => alert(user.name)) // william
  .catch(err => alert(err))

Generators

In progress :)

async/await

Special syntax to work with promises in a more comfortable fashion. Don’t need .then because await handles the waiting for us. Also can use try...catch instead of .catch.

The async keyword before a function makes it always return a promise and allows us to use await in it.

The await keyword before a promise makes JavaScript wait until that promise settles, then either throw error or return the result.

With async/await we rarely need to write promise.then/catch but since still based on promises, sometimes (eg in outermost scope) need to use these methods.

// promises
function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    })
}

loadJson('no-such-user.json')
  .catch(alert); // error: 404

// async/await version
async function loadJson(url) {
  let response = await fetch(url);
  if (response.status == 200) {
    let json = await response.json();
    return json;
  }
  throw new Error(response.status);
}

loadJson('no-such-user.json')
  .catch(alert); // error: 404

Composition over Inheritance

Inheritance is when you design your types around what they are while composition is when you design around what they do.

Commonly said that inheritance (“is-a” relationthip): ex William is a man so he can inherit from the man class. Composition (“has-a” relationship) more like a car has an engine. But this breaks down.

// Inheritance
Dog
  .poop()
  .bark()

Cat
  .poop()
  .meow()

// duplication of poop() so move into shared Animal class
Animal
  .poop()
    Dog
      .bark()
    Cat
      .meow()

// then add functionality
MurderRobot
  .drive
  .kill()

CleaningRobot
  .drive
  .clean()

Animal
  .poop()
    Dog
      .bark()
    Cat
      .meow()

// duplication again so create Robot parent class
Robot
  .drive
    MurderRobot
      .kill()
    CleaningRobot
      .clean()

Animal
  .poop()
    Dog
      .bark()
    Cat
      .meow()

// then later need MurderRobotDog that can kill/drive/bark but can't poop since machine. Screwed! Can't fit MurderRobotDog into inheritance hierarchy nicely except add "another" parent class

This is an example of the guerilla-banana problem where you request a banana but instead get a gorilla holding a banana and the entire forest.

// Composition
dog             = pooper + barker
cat             = pooper + meower
cleaningRobot   = driver + cleaner
murderRobot     = driver + killer
MurderRobotDog  = driver + killer + barker

const barker = (state) => ({
  bark: () => console.log('Woof, I am ' + state.name)
})

const driver = (state) => ({
  drive: () => state.position = state.position + state.speed
})

barker({name: 'fido'}).bark(); // Woof I am fido

// functions share state as parameter so can share the same state
const murderRobotDog = (name) => {
  let state = {
    name,
    speed: 100,
    position: 0
  }
  return Object.assign(
    {},
    barker(state),
    driver(state),
    killer(state)
  )
  return {...barker(state), ...driver(state), ...killer(state)}
}

MurderRobotDog('sniffles').bark(); // Woof, I am sniffles
// Object.assign() takes an object and assigns properties from the other objects into it. Here creates barker, driver, killer into new objects and returns.

Ultimate problem with inheritance is it encourages you to predict the future and build taxonomy of objects very early on in a project.


Functional Programming

Pure Functions

Fundamental to functional programming, a function is only pure if, given the same input, it will always produce the same output and has no side effects.

Referential transparency is the idea that any expression in a program may be replaced by its value without changing the result of the program. This implies that methods should always return the same value for a given argument without any other side effect.

Example in mathematics:

x = 2 + (3 * 4);
// we can replace (3 * 4) with any other combo without changing the result of x
x = 2 + 12;
x = 14;

Memoization

A memoize() function caches the results of a time consuming or expensive operation performed by a function. It can only be used with pure functions which: 1) given the same input always produce the same output, 2) do not rely on external state, 3) do not produce side effects.

This is a common interview question starting point to talk about higher-order functions and closures.

let pureAdd = (x, y) => {
  return x + y;
}

pureAdd(3, 4); // 7
pureAdd(3, 4); // 7 !consistency!

let externalState = 10;
let impureAdd = x => {
  return x + externalState;
}

impureAdd(3); // 13
externalState = 2;
impureAdd(3); // 5 !inconsistency!

Basic example of memoization:

let memoize = fn => { // 1
  let cache = {}; // 2
  return (...args) => { // 3
    let stringifiedArgs = JSON.stringify(args); // 4
    let result = cache[stringifiedArgs] = cache[stringifiedArgs] || fn(...args); // 5
    return result; // 6
  }
}
  1. memoize function accepts a fn to be memoized
  2. Initialize a new cache for the fn using closure scope
  3. Return the memoized fn which accepts some number of args
  4. Stringify the arguments to be used as a “key” to our cache
  5. Check the cache for the value associated with that key. If value exists we assign it to the result, otherwise we call fn with the arguments passed to the memoized function and assign its value to the cache.
  6. Return the result

Now memoize the pureAdd function.

let pureAdd = (x, y) => { return x + y }; // pure function
let memoizedAdd = memoize(pureAdd); // memoized pure function
memoizedAdd(3, 4); // 7
memoizedAdd(3, 4); // 7, efficient and consistent

Currying

Currying is a process to reduce functions of more than one argument to functions of one argument only. It essentially creates a chain of partially applied functions that eventually resolve to a value.

A curry function expects a function as its argument. Then we unpack all the expected arguments (known as its arity).

const notCurryAdd = (x, y, z) => x + y + z; // regular function
const curryAdd = x => y => z => x + y + z; // curry function
notCurryAdd(1,2,3); // 6
curryAdd(1)(2)(3); // 6

Real-world examples of currying:

// bind does currying
// first param thisArg is irrelevant now
increment = add.bind(undefined, 1);
increment(4) === 5;

// react and redux
export default connect(mapStateToProps)(TodoApp)

// event handler reused for multiple fields
const handleChange = (fieldName) => (event) => {
  saveField(fieldName, event.target.value)
}
<input type="text" onChange={handleChange('email')} ... />

ES6 implementation of curry:

const curry = (func, ...args) => args.reduce((f, x) => f(x), func);
curry(add, 1, 2, 3); // 6

const curry = (f, ...args) =>
  (f.length <= args.length) ?
    f(...args) :
    (...more) => curry(f, ...args, ...more)

function volume(l, w, h) {
  return l * w * h;
}

let curried = curry(volume);

curried(1)(2)(3); // 6

A Partial Application lets you create a new function which will allow you to deal only with the undecided arguments without repeating your code.

// Partial Application
multiply = (n, m) => n * m;
multiply(3, 4) === 12; // true
triple = (m) => multiply(3, m); // partial application here!
triple(4) === 12; // true

ES6

Template Literals

Allow empedded expressions, multi-line strings, and string interpolation. They were called “template strings” in previous versions of JavaScript.

Template literals are enclosed by the back-tick ( ) instead of single/double quotes. They can also contain placeholders indicated by the dollar sign and curly braces ${expression}. The expressions in the placeholders and text between them are passed to a function.

Multi-line strings

// normal approach
console.log('string text line 1\n' + 'string text line 2');
// 'string text line 1
// string text line 2'

// template literal approach
console.log(`string text line 1
string text line 2`);
// 'string text line 1
// string text line 2'

Expression interpolation

var a = 5;
var b = 10;
// normal approach
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// template literals
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);

// 'Fifteen is 15 and
// not 20.'

Rest Parameters

The rest parameter syntax allows us to represent an indefinite number of arguments as an array.

function sum(...theArgs) {
  return theArgs.reduce((previous, current) => {
    return previous + current;
  });
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4)); // 10

// can also do
function f(a, b, ...theArgs) { ... }

If the last named argument of a function is prefexed with ... it becomes an array whose elements from 0 to theArgs.length are supplied to the actual arguments passed to the function.

Difference between rest parameters and arguments object

  1. rest parameters are only the ones not given a separate name, while the arguments object contains all arguments passed to the function
  2. the arguments object is not a real array, while rest parameters are Array instances, meaning methods like sort, map, forEach, or pop can be applied to it directly
  3. the arguments object has additional functionality specific to itself (like the callee property).

Spread Operator

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

// function calls
myFunction(...iterableObj);

// array literals or strings
[...iterableObj, '4', 'five', 6];

// object literals
let objClone = { ...obj };

Spread in array literals

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes']; // ["head", "shoulders", "knees", "and", "toes"]

// copy an array
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);
arr2; // [1, 2, 3, 4] updated
arr; // [1, 2, 3] -> unaffected

// a better way to concatenate arrays
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = arr1.concat(arr2); // traditional
arr1 = [...arr1, ...arr2]; // spread operator

Array.unshift is often used to insert an array of values at the start of an existing array.

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// Prepend all items from arr2 onto arr1
Array.prototype.unshift.apply(arr1, arr2); // arr1 is now [3, 4, 5, 0, 1, 2]
arr1 = [...arr2, ...arr1]; // arr1 is now [3, 4, 5, 0, 1, 2]

Spread in object literals

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 }; //Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 }; // Object { foo: "baz", x: 42, y: 13 }

Note that spread syntax can only be applied to iterable objects.

var obj = { key1: 'value1' };
var array = [...obj]; // TypeError: obj is not iterable

Rest syntax looks exactly like spread syntax, but is used for destructuring arrays and objects. In a way, rest syntax is the opposite of spread syntax: spread ‘expands’ an array into its elements, while rest collects multiple elements and ‘condenses’ them into a single element.

Classes

JavaScript classes were introduced in ECMAScript 2015 and are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

Class Declarations

To declare a class you use the class keyword.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Hoisting: Function declarations are hoisted and class declarations are not. You must first declare your class and then access it, or the code will throw a ReferenceError.

var p = new Rectangle(); // Reference Error
class Rectangle {}

Class expressions

Another way to define a class, can be named or unnamed.

// unnamed
var Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name); // "Rectangle"

// named
var Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name); // "Rectangle2"

Sub classing with extends

The extends keyword is used in class declarations or class expressions to create a class as a child of another class.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

var d = new Dog('Fido');
d.speak(); // Fido barks

If there is a constructor present in subclass, it needs to first call super() before using this.

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

var l = new Lion('Fuzzy');
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

Array/Object Destructuring

The destructuring assignment syntax makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30,40,50]

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20

// Stage 3 proposal
({ a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 });
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

Shorthand Property and Method Names


fetch

The fetch() method is available in the global Window scope and starts the process of fetching a resource from the network.

Example…


Web

Site optimization

Minimizing images is the top win, reduce overall HTTP requests (less important with HTTP/2), minify CSS/JavaScript in the footer so page loads faster, use a CDN, implement caching, Gzip compression, add database indices to optimize common queries.

TCP vs UDP

TCP (Transmission Control Protocol) is the most commonly used protocol on the internet. It is a connection-oriented stream over an IP network that guarantees all sent packets reach the destination in the correct order.

A TCP handshake or 3 way handshake occurs between the client and server where the client sends a SYNC (synchronize) request, the server sends back and an ACK (acknowledgment) and sync, and then finally the client sends an ACK back. Once the TCP Handshake is complete, the client and server exchange data (with an ACK after every packet sent, to confirm that the packet safely reached it’s destination with the correct checksum). Once the client and server are done, the handshake is finished and is closed.

TCP must receive acknowledgment packets from the sender and automatically resends any transmissions loss. This causes delays and makes it slow.

UDP (User Datagram Protocol) is a connection-less protocol that is datagram oriented. The only guarantee is the single datagram, which can arrive out of order or not at all. UDP is more efficient than TCP and used for real-time communication (audio, video) where a little percentage of packet loss rate is preferable to the overhead of a TCP connection.

Takeaways

  • TCP is reliable, UDP is unreliable.
  • TCP is stream oriented, UDP is message oriented.
  • TCP is slower than UPD, but UDP has some signal loss.

SSL/TLS

Transport Layer Security (TLS) is the successor to Secure Sockets Layer (SSL) but terms often used interchangeably. Allow for a private/secure connections via secret keys passed between the client/server. Otherwise anyone with access to the physical cables can read the data. Standard practice for e-commerce and general use due to man-in-the-middle attacks.

To initiate a SSL/TLS connection the client sends a “hello” message to the server with a payload of data telling the server which version(s) of SSL/TLS it can support, what encryption ciphers it uses, if it can handle compression, and a string of random bytes used later for some calculations.

The server responds to the client with a “hello” message of its own, saying which version of SSL/TLS it used, a session ID, a copy of its digital certificate, and a random string of byte data. It may also request that the client send back its own digital certificate for verification.

The client then verifies the servers digital certification against a trusted authority and in a response back to the server the client transmits a string of random byte data so the client/server can compute an encryption key to share with one another and encrypt all future traffic.

What Happens When

  1. User enters a URL into browser
  2. Browser looks up IP address for the domain name via DNS
  3. Browser/server use TCP to establish connection
  4. Browser sends an HTTP request to the server
  5. Server sends back an HTTP response
  6. Browser begins rendering the HTML
  7. Browser sends requests for objects embedded in HTML (images, CSS, JavaScript)
  8. Browser sends further async requests

The browser connects to wsvincent.com’s servers and sends an HTTP request that includes a method verb indicating if it wants to read or write data. If you’ve already visited the site before, the request will also send cookies in the header including an expectation to send data back to the browser. The server will receive the request, process it, and send back an HTTP response with a status code indicating success or failure, and a payload of data typically HTML. The browser processes the data and renders it.

Web browser is horribly insecure. Still “fixing it later” mentality. Problem is, Whose interest does the program represent? User? Site? Browser says the site (correct) which does not represent the user.

Cookies

A cookie is a piece of text a server can store on a user’s hard disk in local storage. Data is stored in name-value pairs and can be updated and read.

Cookies are used to solve the problem of state since the web via HTTP is inherently stateless. Practical uses include the ability to remember if a user is authenticated, accurately determine how many visitors to a site, or update shopping cart on e-commerce site.

HTTP Status Codes

  • 200-299 - success, server received and processed request. Most common is 200 for GET and 201 for POST/PATCH/UPDATE.

  • 300-399 - redirects because the client needs to do additional work, usually sent to a new server. Most common is 301 for permanent redirect, 302 for temporary redirect.

  • 400-499 - client error, most common is 404 (resource not found), 400 (payload data was bad), 401 (user not authorized), 403 (request was fine but resource is forbidden).

  • 500-599 - server error, usually due to something being not authorized or forbidden. Most common is 500 (generic error), 504 (timeouts), and 503 (temporary outages).

XSS

XSS (Cross Site Scripting) is a problem because malicious code can exploit existing conventions. Example if insert <script>...</script> into a search field or form, attacker has access to your database.

Same Origin Policy

The same-origin policy restricts how a document or script loaded from one origin can interact with a resource from another origin.

Example of origin comparisons to URL http://example.com/dir/page.html.

http://example.com/dir2/other.html  -> SUCCESS
http://example.com/dir/another.html -> SUCCESS
https://example.com/secure.html     -> FAILURE, different protocol
http://example.com:81/dir/etc.html  -> FAILURE, different port
http://mysite.com/dir/other.html    -> FAILURE, different host

To allow cross-origin access use CORS (Cross-Origin Resource Sharing).

CORS

CORS (Cross-Origin Resource Sharing) is a mechanism that uses additional HTTP headers to let a user agent gain permission to access selected resources from a server on a different origin (domain) than the site currently in use. A user agent makes a cross-origin HTTP request when it requests a resource from a different domain, protocol, or port than the one from which the current document originated.

Simple example: http://domain-a.com makes an <img> src request for http://domain-b.com/image.jpg. Many pages on the web load CSS, images, scripts from separate domains like CDNs. For security reasons, browsers restrict cross-origin HTTP requests initiated within scripts, so unless CORS headers are used can’t access HTTP resources from a different domain.

CORS supports cross-domain requests and data transfers between browsers and web servers. Used with fetch and XMLHttpRequest.

MDN page on CORS

CSRF

In progress :)


DOM (Document Object Model)

What most people hate when they say they hate JavaScript. The browser’s API. The event loop was added to browsers to allow scripts to run.

Events

An event is a signal that something has happened. All DOM nodes generate such signals. Here’s a few common ones:

Mouse events:

  • click - when mouse clicks on an element
  • contextmenu - when mouse right-clicks on an element
  • mouseover/mouseout - when mouse cursor comes over/leaves an element
  • mousedown/mouseup - when mouse button is pressed/released over an element
  • mousemove - when mouse is moved

Form element events:

  • submit - when visitor submits a <form>
  • focus - when visitor focuses on an element, eg <input>

Keyboard events:

  • keydown and keyup - when the visitor presses and then releases the button

Event handlers

To react on events we assign a handler function that runs in case of an event. There are three ways to assign a handler:

  1. HTML attribute: onclick='...'
  2. DOM property: elem.onclick = function
  3. Methods: elem.addEventListener(event, handler) to add, removeEventListener to remove
<!-- html attribute -->
<input value="Click me" onclick="alert('Click!')" type="button">

<!-- DOM property -->
<input id="elem" type="button" value="Click me">
<script>
  elem.onclick = function() {
    alert('Thank you');
  }
</script>

Possible mistakes

Make sure function is assigned not invoked, otherwise result is undefined.

button.onclick = sayThanks; // correct
button.onclick = sayThanks(); // wrong

But in markup we do need brackets. When browser reads the attribute, it created a handler function with the body from its content.

<input type="button" id="button" onclick="sayThanks()">

<!-- above is same as -->
<script>
button.onclick = function() {
  sayThanks();
}
</script>

Event object

When an event happens the browser creates an event object, puts details into it, and passes an argument to the handler.

Some properties of event object:

  • event.type - ex, ‘click’
  • event.currentTarget - element that handled the event

Bubbling and capturing

When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.

See the Pen wjGpQg by William Vincent (@wsvincent) on CodePen.

A click on the inner <p> first runs onclick:

  1. On that <p>
  2. Then on the outer <div>
  3. Then on the outer <form>
  4. And so on till the document object

So if we click on <p> then we’ll see 3 alerts: p -> div -> form.

Almost all events bubble, except for focus

A handler on a parent element can always get the details about where it actually happened. The most deeply nested element that caused the event is called a target element, accessible as event.target.

Event delegation

Capturing and bubbling lead to the powerful event handling pattern called event delegation. If there are lots of elements handled in a similar way, instead of passing a handler to each of them – we put a single handler on their common ancestor.

Browser default actions

There are many default browser actions:

  • mousedown - starts the selection
  • click on <input type='checkbox'> - checks/unchecks the input
  • submit - clicking an <input type='submit'> or hitting Enter inside a form field causes this event to happen, and the browser submits the form after it
  • wheel - rolling a mouse wheel event has scrolling as the default action
  • keydown - pressing a key may lead to an action
  • contextmenu - event happens on a right-click
  • there are more…

All default actions can be prevented if we want to handle the event exclusively by JavaScript. Use either event.preventDefault() or return false on something like onclick.

Many events automatically lead to browser actions. Such as click on a link (goes to URL). A click on a submit button inside a form sends it to the server. Pressing a mouse button over a text selects it.

If we handle an event in JavaScript, often we don’t want browser actions. Two ways to tell a browser not to act:

<a href="/" onclick="return false">Click here</a>
<a href="/" onclick="event.preventDefault()">here</a>

Forms

Special properties and events for forms <form> and controls <input>, <select>, and other.

The submit event triggers when the form is submitted, usually to validate the form before sending it to the server or to abort the submission and process it in JavaScript.


Bit Manipulation

MDN’s section will suffice for now

A good starter video.


Function Fun

Write a swap function.

function swap1(a, b) {
  var temp = a;
  a = b;
  b = temp;
  // [a, b] = [b, a];
  return 'A = ' + a + '. B = ' + b;
}
var x = 1,
  y = 2;
swap(x, y); // A is 2. B is 1.
console.log(x); // 1, x remains 1 though in swap 2

Write three functions for add, sub, mul.

function add(a, b) {
  return a + b;
}

function sub(a, b) {
  return a - b;
}

function mul(a, b) {
  return a * b;
}

Write a function identityf that takes an argument and returns a function that returns that argument.

function identifyf(x) {
  return function() {
    return x;
  };
}

let three = identityf(3);
three(); // 3

Write a function addf that adds from two invocations.

function addf(first) {
  return function(second) {
    return first + second;
  };
}

addf(3)(4); // 7

Write a function liftf that takes a binary function and makes it callable with two invocations.

function liftf(binary) {
  return function(first) {
    return function(second) {
      return binary(first, second);
    };
  };
}

var addf = liftf(add);
addf(3)(4); // 7
liftf(mul)(5)(6); // 30

Write a curry function that takes a binary function and an argument, then returns a function that can take a second argument.

function curry(binary, first) {
  return function(second) {
    return binary(first, second)
  }
}

function curry(binary, first) {
  return liftf(binary)(first);
}

var add3 = curry(add, 3);
add3(4); // 7
curry(mul, 5)(6); // 30

// es6 way to curry variable number of arguments
function curry(func, ...first) {
  return function(...second) {
    return(...first, ...second);
  }
}

Write a function twice that takes a binary function and returns a unary function that passes its arguments to the binary function twice.

function twice(binary) {
  return function(a) {
    return binary(a, a);
  };
}

add(11, 11); // 22
var doubl = twice(add);
doubl(11); // 22
var square = twice(mul);
square(11); // 121

Write reverse a function that reverses the arguments of a binary function.

function reverse(binary) {
  return function(first, second) {
    return binary(second, first);
  };
}

function reverse(func) {
  return function(...args) {
    return func(...args.reverse());
  };
}

var bus = reverse(sub);
bus(3, 2); // -1

Write a function composeu that takes two unary functions and returns a unary function that calls them both.

function composeu(f, g) {
  return function(a) {
    return g(f(a)); // tricky that backwards here
  };
}

composeu(doubl, square)(5); // 100

Write a function composeb that takes two binary functions and returns a function that calls them both.

function composeb(f, g) {
  return function(a, b, c) {
    return g(f(a, b), c);
  };
}

composeb(add, mul)(2, 3, 7); // 35

Write a limit function that allows a binary function to be called a limited number of times.

function limit(binary, count) {
  return function(a, b) {
    if (count >= 1) {
      count -= 1;
      return binary(a, b);
    }
  };
}

var add_ltd = limit(add, 1);
add_ltd(3, 4); // 7
add_ltd(3, 5); // undefined, can only call once

Write a from function that produces a generator that will produce a series of values.

function from(start) {
  return function() {
    var next = start;
    start += 1;
    return next;
  };
}

var index = from(0);
index(); // 0
index(); // 1
index(); // 2

Write a to function that takes a generator and an end value, and returns a generator that will produce numbers up to that limit.

function to(gen, end) {
  return function() {
    var value = gen();
    if (value < end) {
      return value;
    }
    return undefined; // implied but good to state explicitly
  };
}

var index = to(from(1), 3);
index(); // 1
index(); // 2
index(); // undefined

Write a fromTo function that produces a generator that will produce values in a range.

function fromTo(start, end) {
  return to(from(start), end);
}

var index = fromTo(0, 3);
index(); // 0
index(); // 1
index(); // 2
index(); // undefined