JavaScript Guide
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
- Definitions
- Types
- Built-in Objects
- Arrays
- Functions
- Variables
- Operators
- Control structures
- Custom Objects
- Regular Expressions
- Error Handling
- Core Concepts
- Functional Programming
- ES6
- Fetch
- Web
- DOM
- Bit Manipulation
- Function Fun
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:
false
,0
, empty string""
,NaN
,null
, andundefined
are allfalse
.- 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 letterZ
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-insensitiveg
search looks for all matches, without it only the first onem
multiline modeu
enables full unicode supporty
sticky mode: find a match exactly at the position specified by the propertyregexp.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)
, setregexp.lastIndex
to position
To search for all matches:
- an array of matches -
str.match(reg)
, the regexp withg
flag - get all matches with full info about each -
regexp.exec(str)
withg
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, exReferenceError
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 iffe
s 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.
- Default Binding (Function): global context (non-strict mode) or undefined (strict mode)
- Implicit Binding (Method): the object
- Explicit Binding (apply, call, bind): the argument
- New Binding (Constructor): the new object
- 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:
- The JavaScript engine looks for a property called
bark
on thefido
object - It doesn’t find one, so it looks “up the prototype chain” to
fido
’s parent, which isDog.prototype
. - It finds
Dog.prototype.bark
and calls it withthis
bound tofido
.
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 invokedsetTimeout()
is called -> browser puts its callback functionfirstFunction
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:
- if returns a value promise is resolved and closest handler (first argument of
.then
) is called with that value - if throws an error, new promise is rejected, and closest handler (second argument of
.then
or.catch
) is called with it - 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
}
}
memoize
function accepts afn
to be memoized- Initialize a new cache for the
fn
using closure scope - Return the memoized
fn
which accepts some number of args - Stringify the arguments to be used as a “key” to our cache
- 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. - 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
- rest parameters are only the ones not given a separate name, while the
arguments
object contains all arguments passed to the function - the
arguments
object is not a real array, while rest parameters areArray
instances, meaning methods likesort
,map
,forEach
, orpop
can be applied to it directly - the
arguments
object has additional functionality specific to itself (like thecallee
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
- User enters a URL into browser
- Browser looks up IP address for the domain name via DNS
- Browser/server use TCP to establish connection
- Browser sends an HTTP request to the server
- Server sends back an HTTP response
- Browser begins rendering the HTML
- Browser sends requests for objects embedded in HTML (images, CSS, JavaScript)
- 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
.
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 elementcontextmenu
- when mouse right-clicks on an elementmouseover/mouseout
- when mouse cursor comes over/leaves an elementmousedown/mouseup
- when mouse button is pressed/released over an elementmousemove
- 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
andkeyup
- 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:
- HTML attribute:
onclick='...'
- DOM property:
elem.onclick = function
- 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
:
- On that
<p>
- Then on the outer
<div>
- Then on the outer
<form>
- 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 selectionclick
on<input type='checkbox'>
- checks/unchecks theinput
submit
- clicking an<input type='submit'>
or hittingEnter
inside a form field causes this event to happen, and the browser submits the form after itwheel
- rolling a mouse wheel event has scrolling as the default actionkeydown
- pressing a key may lead to an actioncontextmenu
- 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
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