JavaScript
Basic structuresβ
- three declarators:
var, const, let
- don't use
var
anymore, as it's hoisted
Assignmentβ
// creating a new object
let person = {
name: 'Kunio Otani'
}
// re-assignment of a variable
person.name = 'Joe Bridge'
// creating a new variable
let city = 'New York';
// creating a regular function
// regular functions are not recommended anymore
let myFunc = function() {
return 42;
}
// creating an arrow function
let myFunc2 = () => {
return 42;
}
// equivalent (in case the function only have a return statement)
let myFunc2 = () => 42;
Six falsy valuesβ
- any other values is true
false
,0
,''
,null
,undefined
,NaN
Six primitive values (+ Object)β
- Boolean, Null, Undefined, Number, String, Symbol
Argumentsβ
- common way - arguments are listed
function displayTags(tags) {
for(let tag of tags) {
...
}
}
arguments
keyword (not a very good approach)
// "arguments" is a reserved keyword for arguments of a function
function displayTags() {
// e.g., we can call displayTags(1, 2, 3)
for(let i in arguments) {
...
}
}
- rest parameter syntax
// - allows to represent an indefinite number of arguments as an Array
function displayTags(targetElement, ...tags){ // ... always goes last
for(let i in tags) { ... }
}
// spread operator will convert an array into individual elements
const tags = ['tag1', 'tag2'];
displayTags(myElement, ...tags);
For loopsβ
- for-in - works for any object, not only arrays
- The ECMAScript (JavaScript) language specification states that the order of enumeration of an object is undefined - not recommended to use
for(let key in activeUsers) {
console.log(activeUsers[key])
}
- for-of - works only for objects that have
[Symbol.iterator]
- works for arrays out of the box
for(let user of activeUsers) {
console.log(user)
}
Destructuring assignmentβ
Array destructuringβ
- allows to extract multiple array elements and store them into variables
// name1 and name2 are new variables that will contain first two items in the array
let [ name1, name2 ] = names
- ignoring second element
let [ name,, name3 ] = names
- setting default values (in case they are undefined)
let [ a = 1, b = 2, c = 3 ] = names
- extracting sub-arrays
// extracts first item and the rest of the array as a new array
let [first, ...rest] = users;
Object destructuringβ
- allows to extract attributes
// assign properties - bad way
let user = buildUser('Sam', 'Williams')
let first = user.first
let last = user.last
// assign properties - good way
let { first, last, fullName } = buildUser('Sam', 'Williams')
// in case we only need fullName
let { fullName } = buildUser('Sam', 'Williams')
// works for functions too
function setThread(name, options = {}) {
let { param1, param2 } = options;
}
Template stringsβ
// ` backtick must be used!!!
let fullName = `${first} ${last}`
Optional Chainingβ
- can save us a few if-checks
// if submission is undefined or null, it will assign that value to the result
// saves us a lot of ifchecks, as it doesn't throw an error
let result = submission?.holding?.name;
// equivalent
let result;
if(submission && submission.holding) {
result = submission.holding.name;
}
Default valueβ
- default value of a function parameter
// default value
function loadProfiles(userNames = []) {
}
- default primitive value
// if options.container is a falsy value (see above), it will get .timer-display
let container = options.container || ".timer-display";
// another option - if options.container is null or undefined, it will get .timer-display
let container = options.container ?? ".timer-display";
- default object value
let defaults = {
...someDefaultObject, // this will copy-paste everything from someDefaultObject
param1: value1, // here we can re-declare what we need
param2: value2
}
// !!! order matters. Here, param1 and param2 will be replaced by what is inside someDefaultObject
let defaults = {
param1: value1,
param2: value2,
...someDefaultObject,
}
Rest and Spread operatorsβ
- denoted with three ellipses
...
- rest is used to represent an infinite number of arguments
- spread is used to allow an iterable object to be expanded
- rest destructuring
function fn(num1, num2, ...args) { } /// args always goes last
- destructured first three parameters
function fn(...[n1, n2, n3]) {}
- spread operator
function myFunction(n1, n2, n3) { ... }
const values = [ 1, 2, 3 ];
// spread
myFunction(...values);
- object destructuring
const { firstNamne, lastName } = obj
- destructuring into a new variable
const { firstName: first, lastName } = obj
console.log(first)
Arrow functionsβ
- are callable but not constructable (you can't use
new
operator)
const myArrowFunction = () => {
console.log('Hello from arrow function');
}
// arrow function that returns an object
// parentheses need to be used ()
const myArrowFunction = () => ({ something: 3 });
// WRONG! This would be considered a function body
const myArrowFunction = () => { something: 3 };
Array operationsβ
- mutating arrays
- these functions mutate the original array:
copyWithin, fill, pop, push, reverse, shift, sort, splice, unshift
- these functions mutate the original array:
const mutatingAdd = [1, 2, 3]
// adding a new item to an array
mutatingAdd.push(4) // [1, 2, 3, 4]
// equivalent, as this doesn't create a new array (compiler optimization)
mutatingAdd = [...mutatingAdd, 4]
// unshift will add an item to the beginning
mutatingAdd.unshift(0) // [0, 1, 2, 3, 4]
// equivalent of unshift
mutatingAdd = [0, ...pokemon]
- immutable operations
const arr1 = [1, 2]
const arr2 = [3, 4]
const arr3 = arr1.concat(arr2) // [1, 2, 3, 4]
const arr3 = [...arr1, ...arr2]// [1, 2, 3, 4]
const arr4_altern = [0, ...arr1] //[0, 1, 2]
find()
- returns the first element that satisfies a testing function
let admin = users.find((user) => { return user.admin; });
// a common error: passing an object instead of an arrow function
// this will not work
let admin = users.find(adminUser)
- splice - removes strings from an array
const arr = [0, 1, 2]
// will mutate the original array
const tailArr = arr.splice(-1) // [2]
console.log(arr) // [0, 1]
- slice - creates a subarray
const numbers = [0, 1, 2, 3, 4]
const lessThanThree = numbers.slice(0, 3) // [0, 1, 2]
const moreThanTwo = numbers.splice(2, numbers.length) // [2, 3, 4]
- sorting arrays
// sorting function:
// if < 0, then a < b if == 0, then a == b if > 0, then b > a
nums.sort((a, b) => {
return a - b; // ascending, only if a and b are numbers
});
nums.sort((a, b) => {
return b - a; // descending, only if a and b are numbers
});
- generating a sequence
const indices = Array.from(Array(10).keys())
console.log(indices) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Mapsβ
- when using objects as maps, their keys are always converted to strings
let obj = {};
obj[1] = 2;
obj['1'] = 3; // will replace obj[1]
- unlike objects, Maps can't be serialized into JSON, yet they can be iterated via for-of loop (objects can't)
let totalReplies = new Map();
totalReplies.set(user1, 5) ;
totalReplies.set(user2, 42);
let has = totalReplies.has(user1);
totalReplies.delete(user1);
- iterating over entries
// the iterator returns an array of pairs. That's why we can use let [key, value]
for(let [key, value] of mapSettings) {
console.log(`${key} = ${value}`);
}
Setsβ
- common sets - they don't contain duplicate values
let tags = new Set()
tags.add('JavaScript')
tags.add({ version : '2015' })
let [first] = tags // first item
Classesβ
- class syntax (introduced in ES6)
class SponsorWidget {
myVar = 12
myOtherVar
constructor(name description, url){
// another way how to initialize variables
this.name = name
this.description = description
this.url = url
}
render(){
let link = this._buildLink(this.url)
}
// underscore is a convention for private method, not used in TypeScript
_buildLink(url) {
}
}
// inheritance
class SponsorWidget extends Widget {
constructor(name, description, url) {
super()
}
render() {
super.render() // parent version
let parsedName = this.parse(this.name)
let css = this._buildCss()
}
}
Promisesβ
- three states - pending, fulfilled, rejected
- handlers -
then()
,catch()
,finally()
- when
Promise.then()
is called, it returns a new promise in the pending state - when
Promise.catch()
is called, it internally callsPromise.then(undefined, rejectHandler)
const myPromise = new Promise((resolve, reject) => {
reject(new Error());
})
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => { resolve('Done!')} ) // also works
})
Error handlingβ
myPromise.then(() => {
// success handler
}, (error) => {
// error handler -> the second argument
console.log(error);
})
Promise.resolve('Resolve').then(console.log) // prints Resolve
Promise.reject('Reject').catch(console.log) // prints Reject
Promise chainingβ
getPollResultsFromServer("Sass") // pending
.then(ui.renderScript) // fulfilled, read results
.then(doSomethingElseNonBlocking) // fulfilled
.catch((error) => { // rejected
console.log("Error: ", error);
});
function getPollResultsFromServer(pollName){
return new Promise(function(resolve, reject){
// this will go to the next THEN function
resolve(someValue);
// this will go to the next CATCH function
reject(new Error('There is no spoon');
});
}
Combined promisesβ
- Promise.all waits until all promises have been fulfilled
- Promise.race waits until the first promise has been fulfilled
await Promise.all([
resolveAfter1Second(),
resolveAfter2Seconds(),
])
Async/awaitβ
async
function returns a promise - all async functions can havethen()
andcatch()
handlersawait
can be used only within anasync
functionusing async/await
// await proceeds to the next line of code when an async function has been completed/fulfilled
// returns an array of fulfilled values when all inner promises have been fulfilled
const myPromise = await Promise.all([
(async() => await resolveAfter1Second()),
(async() => await resolveAfter2Seconds()),
]);
- if we don't return a promise, JS will do it automagically!
async function example1() {
return 'Hello' // JS will wrap this into a promise
}
- error rejection
try {
const value1 = await Promise.reject('Error')
} catch(err) {
}
- event loop example
- Promises are put in the microtask queue that has a high priority
console.log('Synchronous 1');
setTimeout(() => console.log('Timeout 2'), 0);
Promise.resolve().then(() => console.log('Promise 3'));
console.log('Synchronous 4');
// correct order:
// Synchronous 1
// Synchronous 4
// Promise 3
// Timeout 2
- combining async and await
const makeSmoothie = async() => {
try {
const a = getFruit('pineapple');
const b = getFruit('strawberry');
const result = await Promise.all([a, b]);
return result;
} catch(err) {
console.log(err);
}
}
- async and map
- don't use
.map(async () => ... )
. Instead, iterate in a for-loop
- don't use
const fruitLoop = async() => {
for await(const emoji of smoothie) {
log(emoji)
}
}
Modulesβ
- creating modules
// flas-message.js
export default function(message){
alert(message)
}
// app.js
import flashMessage from './flash-message'
flashMessage('Hello')
- named exports
// flash-message.js
export function alertMessage(message){
alert(message);
}
export function logMessage(message){
console.log(message);
}
// app.js
import { alertMessage, logMessage } from './flash-message';
- importing the whole module
import * as flash from './flash-message';
flash.alertMessage('Hello');
- mixing default and named exports
// file mojo.js
const A = () => console.log('A');
export default A;
export const B = () => console.log('B');
export const C = () => console.log('C');
// file dojo.js
// a module can only have one default export, but many named exports
import A, { B, C } from 'mojo.js'; // A is a default export, B and C are named exports
- exporting class modules
// exporting class modules
export default class FlashMessage { ... }
// importing class modules
import FlashMessage from './flash-message';
// alternative
export { FlashMessage }
import { FlashMessage }
Advanced structuresβ
Curried functionsβ
- multiple arrow functions, can be used to wrap event handlers with additional parameters
const three = a => b => c => a + b + c
three(1)(2)(3)
// equivalent:
const three = (a) => {
return (b) => {
return (c) => {
return a + b + c
}
}
}
Iteratorsβ
- we have to define a function that takes a collection in as the parameter and returns an object which must have property
next
- when
next
is called, the iterator steps to the next value in the collection and returns an object with the value and the done status of the iteration
function createIterator(array){
let currIdx = 0
return {
next() {
return currIdx < array.lenth ? {
value: array[currIdx++], done: false,
} : { done: true }
},
}
}
Generatorsβ
- provide an iterative way to build a collection of data
- can be used for asynchronous processing also
function *nameList(){
yield 'Sam' // { done : false, value: 'Sam' }
yield 'Tyler' // { done : false, value: 'Tyler' }
// if there is no return, the last value will be undefined
return 'Mojo'; // { done : true, value: 'Mojo '}
}
const generator = nameList(); // will create a generator
for(let name of generator) {...} // will iterate over yield statements
let names = [...generator]; // also possible, yet each generator can only be used once
// another way -> manually iterating over yield statements
while(!generator.done) {
let val = generator.next(); // manually fetching values
}
- infinite loop
- possible when using generators, as each yield will pause it
function *gen() {
let i = 0
while(true) {
yield i++
}
}
Tagged template literalsβ
- can be parsed with tag functions and can return a manipulated string
function tagFunction(strings, param) {
return strings[0] + param
}
const tagged = tagFunction`We have ${num} param`
Readonly propertiesβ
let a = {}
Object.defineProperty(a, 'mojo', {
value: 15,
writable: false
})
Getters and settersβ
function Foobar () {
var _foo // true private property
Object.defineProperty(obj, 'foo', {
get: function () { return _foo },
set: function (value) { _foo = value }
})
}
Proxiesβ
function Foo() {
return new Proxy(this, {
get: function (object, property) {
if (Reflect.has(object, property)) {
return Reflect.get(object, property)
} else {
return function methodMissing() {
console.log(`You called ${property} but it doesn't exist!`)
}
}
}
})
}
Tips and tricksβ
- conditional property assignment
- in cases we want an object to either have the property or not at all
function assign(size) {
// if parameter is undefined, size won't be present at all
const myObj = {
radius: 12,
...(size ? { size: size } : {})
}
}
- transforming arguments object into an array
const argArray = Array.prototype.slice.call(arguments)
- avoiding repetition
// repetition
return { first: first, last: last, fullName : fullName }
// without repetition (works only when properties have the same name)
return { first, last, fullName }
- deep cloning
- very bad way, as it will transform the object into a string and back
- better to use some dedicated cloning libraries
JSON.stringify(myObj1) === JSON.stringify(myObj2)
- semicolons
- no longer mandatory, except form a few cases
// this prevents any previous code from
// executing your code as the arguments to that function
// therefore, the semicolon has to be there
;(async () => { ... }
- avoid using
new Array()
const a = new Array(10);
a.push(12); // length is 11 !!!
a.toString(); // returns ,,,,,,,,,12
const a = [10]; // recommended approach! Don't use new Array()
- math operations
// Math operations
Math.trunc(5.5) = 5 // returns integer part, very fast, added in ES6
Math.ceil(5.5) = 6 // rounds up
Math.floor(5.5) = 5 // rounds down
Math.round(5.5) = 6 // rounds with respect to the fraction part
- remove duplicities from an array
const arr = [...new Set([1, 2, 3, 3])] // [1, 2, 3]
- get the last item in an array
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array.slice(-1)) // Result: [9]
- assigning operators in a constructor
class Polygon {
constructor(options) { // many attributes
Object.assign(this, options)
}
}
- freezing properties of an object
const obj = { foo1: 'bar1', foo2: { value: 'bar2' } };
Object.freeze( obj );
obj.foo = 'foo'; // doesn't change the property, doesn't throw Error
obj.foo2.value = 'bar3'; // does change the value - it's nested!
Console tipsβ
- displaying variables:
console.log({foo, bar, baz})

- using CSS styles:
console.log('%c Hello', 'color: orange;');

- using tables:
console.table([foo, bar, baz]);

- measuring time:
console.time('looper');
console.timeEnd('looper');

- tracing method:
console.trace('Did I forget sth?');

Weird parts of JavaScriptβ
false.toString(); // false
function Foo() { }
Foo.bar = 1;
"π".length === 2; // true. However, it's a single element in a for-of loop
2.toString(); // SyntaxError, dot here means a floating point literal
(2).toString(); // OK
2 .toString(); // OK
2..toString(); // OK and What the Fuck
const foo = {}; // a new object, derives from Object.prototype
// property access
const foo = { name : "kitten" }
foo.name; // OK
foo["name"]; // OK
const get = "name";
foo[get]; // OK
foo.1234; // ERROR
foo["1234"]; // OK
// delete is the only way how to remove a property
// we can't delete global variables !!!!
// undefined or null only removes the VALUE
const obj = { bar: 1 };
obj.bar = undefined; // removes value
delete obj.bar; // removes key
// anonymous namespaces -> NOT USED ANYMORE! It's yuck yuck forever!
(function () {
// a self contained namespace
window.foo = function() {
// exposed closure
};
})(); // execute immediately
// more about arrays
new Array(3); // [],
new Array('3'); // ['3']
// equality operator. A leftover from a bigone era when JavaScript was built by morons and idiots
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
{ } === {} // false
new String("foo") === "foo" // false
10 == "10" // true
10 == "+10" // true
10 == "010" // true
isNan(null) == false// true, null converts to 0
new Number(10)===10 // false, object and number
// typeof operator
"foo" // string
new String("foo") // string
true // boolean
[1, 2, 3] // object
new Function() // function
// casting
'' + 10 === '10' // true
!!'foo'; // true
!!true; // true
// boolean evaluation
new Boolean() // false
new Boolean(0) // false (for any falsy parameter)
// but careful, (new Boolean(anything) === true/false) -> always FALSE
new Boolean(true) // true (for any truthy parameter)
// plus operator - converts a string to number
+'9.11'
+true // 1
+'123e-5' // returns 0.00123
// !! operator (bang bang)
// converts anything to boolean
!!null // false
!!undefined // false
!!true // true
!!"" // false
!!"string" // true
// tilde operator - bitwise NOT operator
if(~username.indexOf("Drake")) { ... }