Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save SriniTechHub/cd2af013a72a2f798147642a7fc5666b to your computer and use it in GitHub Desktop.

Select an option

Save SriniTechHub/cd2af013a72a2f798147642a7fc5666b to your computer and use it in GitHub Desktop.
Advanced Modern JavaScript Guide

Advanced Modern JavaScript Guide

Table of Contents

Guide Overview


Keywords and Operations

Keywords and Operations Overview

Identifiers: var, let, const

Arrow Functions

rest Operator

spread Operator

Exponentiation Operator

this Keyword

new Keyword

Imports


Abstract Concepts and Design

Abstract Concepts and Design Overview

Singleton

Closures

Callbacks

Higher Order Functions

Event Loop and Queue

Promises

Generators

Asynchronous JavaScript and XML (AJAX)

Async Functions


Objects

Objects Overview

Object Function Declarations

Computed Property names

Native JavaScript Prototypes

Destructuring

Object.assign

Hash Maps

Sets

Object Rest

Object Spread


Arrays

Arrays Overview

from

push

pop

shift

unshift

for each Loops

for in Loops

for of Loops

map

filter

some

every

reduce

find

findIndex

includes


Numbers

Numbers Overview

isFinite


Strings

Strings Overview

String Templating

padStart

padEnd


Classes in JavaScript

Classes in JavaScript Overview

class Keyword

Inheritance

super


Methods

Methods Overview

Default Parameters

Guide Overview

TOC Shortcut

JavaScript used to have updates far and in between. Recently, the European Computer Manufacturers Association (ECMA) has committed to regular, annual updates starting with ES 2015 (a.k.a. ES6). As of 2020, we are on ES 2019 (ES10). This guide outlines much of the new functionality.


Keywords and Operators

Identifiers

TOC Shortcut

JavaScript used to have a single identifier: 'var'. Now, it has three. These are listed in order of ascending preference.

const

'const' is used to create constants; values that cannot be re-declared. Unlike other languages, 'const' is mutable, but it is not re-assignable. We can declare some array 'const arr' and push to it, but it will forever remain an array.

It is a good practice to use const whenever possible.

let

'let' is a context specific variable identifier. It respects the block scope that it is in and is not hoisted to the top of the context. This is our most disposable variable identifier.

If const is not appropriate, it is good practice to use let.

var

'var' gets hoisted to the top of its context. This means, no matter where you declare it in the block, when the script runs, var is treated as though it was declared at the beginning of the block. This can create issues with looping and timeouts. So, what was once the standard and only identifier in JavaScript should now be used the least. We have far more scope and mutability control with 'let' and 'const'.

If let is not appropriate, it is better to refactor code to accommodate const or let, rather than use var, which is similar to declaring a global.

Arrow Functions

TOC Shortcut

Arrow functions provide a shorthand means of writing functions and anonymous functions.

const foo = () => {
    return "bar"
}

const timesTwo = params => params * 2

rest Operator

TOC Shortcut

The rest operator, '...', collects the remaining arguments to a function and returns them to us in an array.

Note: When the rest operator is not used as a parameter it is called a spread.

const printArguments = (a, b, ...c) => {
	console.log(a); // 1
	console.log(b); // 2
	console.log(c); // [1, 2, 3, 4, 5]
}

printArguments(1, 2, 3, 4, 5);

Why is this useful?

We may work on problems where we need to iterate over all of the arguments in an array. However, the 'arguments' keyword is not an array. Instead, we can use 'rest' to pass an array of arguments. This makes it far easier to use forEach and forIn loops without further processing.

Previously, we would need to do something like:

const sumArguments = () => {
	const total = 0;
	
	for(let i = 0; i < arguments.length; i++) {
		total += arguments[i];
	}
	
	return total;
}

But now, we can simply use rest to turn n arguments into an array of arguments. We use the new array function, reduce(), to add each value to an accumulator value, which is returned out. We can now pass n number of arguments and have a means of turning it immediately into an array of n elements.

Note: See the reduce() section for more on reduce's functionality.

const sumArguments = (...args) => args.reduce(acc, next) => acc + next);

spread Operator

TOC Shortcut

While the rest operator is used in function parameters, spread is used everywhere else. We use it to "spread out the values": The spread operator is used on arrays to spread each value out as a comma separated value. It is useful when you have an array, but what you are working with expects comma separated values.

// ES5
const combined = arr1.concat(arr2).concat(arr3);
// is the same as ES 2015's (ES6's)
const combined = [...arr1, ...arr2, ...arr3];

This is useful when we have an array of values but are dealing with a function that accepts values individually. Before ES 2015 we would use apply(). Here, Math.max is looking multiple parameters; not an array of them. We can use spread to separate them into comma delimited values:

const arr = [3, 2, 4, 1, 5];
Math.max(arr); // NaN
Math.max.apply(this, arr); // 5 with ES 5 syntax
Math.max(...arr); // 5 with ES 2015 syntax

Below, sumValues accepts three values, and we have an array of three values. Instead of passing each element individually we can use the spread operator to delimit the array on entry.

const sumValues = (a, b, c) => {
	return a + b + c;
}

let nums = [12, 15, 20];

console.log(sumValues(...nums)); // 47, because each value gets assigned to a, b, c

The spread operator is a means of interacting with an array without mutating it, which in turn creates functional programming opportunities.

var arr1 = [1, 2, 3]
var arr2 = [4, 5, 6]

console.log([...arr1, ...arr2]) // [1, 2, 3, 4, 5, 6]

var places = ['New Haven', 'Northampton', 'Sacramento']

var iLive = ([...arr]) => {
    console.log(`I live in ${arr.reverse()[0]}`) // Sacramento
}

var haveLived = ([...arr]) => {
    console.log(`But I have lived in ${[arr[0], arr[1]].join(", ")}`) // Northampton, New Haven
}

iLive(places)
haveLived(places)

console.log(`[${[...places].join(", ")}]
// remains in standard order despite the spreadwise reversal.`)

Exponentiation Operator

The exponentiation operator, double asterisks or **, is used for exponent math.

// ES 2015
let calculatedNumber = Math.pow(2, 4); // 16

// ES 2016
calculatedNumber = 2**4; // 16

calculatedNumber **= 2;

this Keyword

TOC Shortcut

It is a reserved keyword that is detrmined by how a function is called; its execution context. We use four rules to determine execution context:

  1. Global
  2. Object/Implicit
  3. Explicit
  4. New

This is not specific to JavaScript. However, JavaScript gives us some tools to redefine what 'this' is referring to, which is covered below. I have been told that this is not often used in production.

Rules

Global

If 'this' is used outside of a declared object, it refers to its global context. In the browser, 'this' refers to the window, which in turn, owns all of the other objects in the DOM. So, one could use this in a global context to find anything in the DOM.

function whatIsThis() { return this }
function variablesInThis() { this.person = "Brandon" }
console.log(person) // Brandon
whatIsThis() // window

Global with Strict

If we write 'use strict' at the top of our script, we prevent use of the global context. This means that if we try to access this in a function context without an object it will be undefined. This prevents us from accidentally creating global variables, but means that we can only use this in the context of a specific object e.g. a JSON.

function whatIsThis() { return this }
function variablesInThis() { this.person = "Brandon" }
console.log(person) // TypeError, can't set person on undefined
whatIsThis() // undefined

Object/Implicit

When 'this' is inside of a declared object, 'this' refers to that object or the closest parent object.

var person = {
   name: "Brandon",
   hello: () => {return `Hello ${this.firstName}`} }, // "Hello Brandon"
   determineContext: () => {return this === person }, // True
   pet: {
       talk: () => { return `Make noise ${this.name}` // Undefined, because pet doesn't own name.
       determineContext: () => {return this === person }, // False, because this is a pet, not a person.
   }
}

If we use 'this' in person, we access person. If we use 'this' in pet, we access pet.

Explicit

We can use Call, Apply, and Bind to solve these execution context problems by being explicit about what we want 'this' to refer to.

Call
  1. Invoked immediately
  2. call(thisArg, a, b, c, ..., n)

Operations are invoked immediately invoked.

'call' can be used to reduced repetitive code. Both objects below have a hello function that does the same thing. We can reuse the first object's hello function by explicitly declaring what 'this' should be.

var brandon = {
   name: "Brandon",
   hello: () => {return `Hello ${this.firstName}`} }, // "Hello Brandon"
}
var jenny = {
   name: "jenny",
   hello: () => {return `Hello ${this.firstName}`} }, // "Hello Brandon"
}

brandon.hello.call(jenny) // 'this' == jenny, and will thus print "Hello Jenny" despite hello's implicit 'this' object being brandon.
Apply
  1. Invoked immediately.
  2. apply(thisArg, an array of args)

Similar to apply, but only takes two arguments; we store additional arguments in an array.

var brandon = {
   name: "Brandon",
   addNumbers: (a, b, c, d) => {return this.name + " just calculated " + a + b + c + d }
}
var jenny = {
   name: "jenny",
}

brandon.hello.apply(jenny, [4, 5, 6, 7]) // Jenny just calculated 22
Bind
  1. Not invoked immediately.
  2. bind(thisArg, a, b, c, ..., n)

When bind is called later, it will know what 'this' should be. This is often used in asynchronous operations and is essential for currying. We can use bind when we are not sure what the arguments will be.

var brandon = {
   name: "Brandon",
   addNumbers: (a, b, c, d) => {return this.name + " just calculated " + a + b + c + d }
}
var jenny = {
   name: "jenny",
}

var jennyCalcs = brandon.addNumbers.bind(1, 2, 3, 4)

jennyCalcs() // Runs when we call it instead of immediately. Returns 10.

var jennyCalcsAgain = brandon.addNumbers.bind(jenny, 1, 2)

jennyCalcsAgain(3, 4) // Again, runs when called, but here we add some additional arguments.
// Takes the initially bound 1,2 and combines it with this 3,4. Returns 10. 
Bind with a Set Timeout

While setTimeout is inside an object, because of the delay, 'this' no longer refers to brandon. After the delay, 'this' will instead refer to the global 'this'. In a browser, this would be the 'window' object.

However, we can solve this problem with a bind.

var brandon = {
   name: "Brandon",
   hello: () => 
       {setTimeout( ()=> {
           () => { return `Hello ${this.firstName }`} // "Hello Brandon"
      }, 1000)
   },
}

We can solve this with bind. Here, we explicitly define what this is.

var brandon = {
   name: "Brandon",
   hello: () => 
       {setTimeout( ()=> {
           () => { return `Hello ${this.firstName }`} // "Hello Brandon"
      }.bind(this), 1000)
   },
}

new Keyword

The 'new' keyword is a reserved keyword. It immediately creates a new object and this will also immediately refer to this new object.

function Person(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
}

var brandon = new Person ("Brandon", "K")

Here, despite the shared variable names, 'this' will refer to the object properties; not the parameters. In fact, we have to use the new keyword, or else we will get a type error. We do not want to overwrite the existing Person function (not that we could), but instead, we want to create a brand new Person using this template.

Imports

TOC Shortcut

// ==============================================================
// Standard Import and Export Examples
// ==============================================================
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name");

// Exporting individual features
export let name1, name2, , nameN; // also var, const
export let name1 = , name2 = , , nameN; // also var, const
export function functionName(){...}
export class ClassName {...}

// Export list
export { name1, name2, , nameN };

// Renaming exports
export { variable1 as name1, variable2 as name2, , nameN };

// Exporting destructured assignments with renaming
export const { name1, name2: bar } = o;

// Default exports
export default expression;
export default function () {  } // also class, function*
export default function name1() {  } // also class, function*
export { name1 as default,  };

// Aggregating modules
export * from ; // does not set the default export
export * as name1 from ;
export { name1, name2, , nameN } from ;
export { import1 as name1, import2 as name2, , nameN } from ;
export { default } from ;

// ==============================================================
// Node.js Import and Export Examples
// ==============================================================
modules.export = yourModule or yourModule() // Node.js EXPORT
const yourModule = require("yourModule) ' Node.js IMPORT

Abstract Concepts and Design

Abstract Concepts and Design Overview

TOC Shortcut

Some of these concepts may or may not be specific to JavaScript. Callbacks are common in every language, while Closures refer specifically to a means of JavaScript data encapsulation.

Singleton

A singleton is an object and there can only be one instance of this object. In JavaScript, on getInstance, we create an instance if it doesn't exist. If it does exist, we return that instance.

var Singleton = (function () {
    var instance

    function createInstance() {
        var object = new Object("I am the instance")
        return object
    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance()
            }
            return instance
        }
    }
})()

var instance1 = Singleton.getInstance()
var instance2 = Singleton.getInstance()

alert("Same instance? " + (instance1 === instance2))

Closures

TOC Shortcut

A closure is a function that uses variables defined in outer functions that have previously returned. If the inner function does not make use of any outer variables it is not considered a closure.

function outer() {
    var data = "closures are "
    return function inner() {
        var innerData = "awesome"
        return data + innerData
    }
}

console.log(outer() ) // Prints the inner function. We still need to treat it like a function, so ...
console.log(outer()() ) // Don't forget the additional '()'!
// Calls the outer, then immediately calls the inner: "closures are awesome"

The inner() function can see the data variable, so it concatenates that with innerData and returns it all the way out. Note that we must explicitly call that inner function. outer() returns a function, so calling '()' after will evaluate that return as a function.

Here is another example.

// Addition w/ Closure Example

var outer = (a) => {
  return (b) => {
    // The inner function is making use of the variable 'a'
    // This aws defined in the outer function.
    // By the time this is called, that outer function has already returned.
    // This inner function is a closure.
    return a + b
  }
}

console.log(outer(1)(2)) // 3

var storeSum = outer(5)(5) // 10
storeSum(10) // 15
var counter = () => {
    var count = 0
    return () => {
        return ++count
    }
}

var c = counter()

c() // 1
c() // 2
c() // 3

// We can create a new instance of counter() to track a separate counter.

The counter function keeps track of its own count and we are not allowed to access it. However, each time we call it, it increments count by 1. This means our counter remains protected; we have more privacy.

var company = () => {
    var employees = ["Brandon", "Jenny"]
    return {
        getEmployees: () => {
            return employees
        },
        addEmployee: (name) => {
            employees.push(name)
            return employees
        }
    }
}

c = company()
c.getEmployees() // Brandon, Jenny
c.addEmployee("Phil") // Phil
c.getEmployees() // Brandon, Jenny, Phil

The list of employees is protected, but we can still interact with it. This getter and setter pattern yields object oriented design encapsulation and privacy benefits.

Callbacks

TOC Shortcut

A callback function is a function that is passed into another function as a parameter and invoked by that function at some time.

const foo = callback => {
    console.log("foo")
    callback()
}

const bar = () => {
    console.log("bar")
}

foo(bar)

// echoes foo bar, because foo() calls bar() as its callback

Higher Order Functions

TOC Shortcut

Accept a callback function as a parameter.

Event Loop and Queue

TOC Shortcut

The queue is an ordered list of functions waiting to be placed on the call stack. The Event Loop is functionality in the JavaScript runtime that checks the queue when the stack is empty. If the stack is empty, the front of the queue is placed on the stack.

Consider the following:

function square(n) {
    return n * n
}

setTimeout(function() {
    console.log("Callback placed on queue.")
}, 0)

console.log(square(2))

One would tink that setTimeout's function callback gets placed ahead of the square(2) call, but it does not. function main() gets called, setTimeout() gets called and adds the callback to the queue, square(2) gets called, and then finally we get the callback from the queue despite it's 0 timeout.

Promises

A promise is an object that represents a task that will be completed in the future. It is asynchronous. We can use callbacks to handle incomplete and complete promises based on whether or not we were successful. Taking a number at a help counter is like a promise; you are being told you will receive help at some point. The help you receive when it is your turn is like the invocation of the callback.

In sum:

  • Promises are a one time guaranteed return of some future value
  • When the value is figured out, the promise is resolved/fulfilled or rejected
  • A friendly way to refactor callback code

Using the promise constructor is less common. We typically use other libraries, like jQuery, to make promises, and handle them with .then() and .catch() chained on.

const promise1 = new Promise(function(resolve, reject) {
    resolve([1, 2, 3, 4]);
    reject("Error");
})

promise1.then((arr) => {
    console.log("Promise resolved with data: " + arr);
}).catch(() => {
    console.log("Promise rejected with data : " + arr);
})

Promise Chaining

We can chain multiple promises together. This is critical when some initial promise relies on the completion of some other promise.

Example

In my app, Backlogged, there are a lot of HTTP POST requests that update the content for various video game properties. For example, if I want to delete a game, I also want to delete all comments associated with that game. First, I search for the game in the database. If that game exists, I proceed to deleting comments associated with that game, if those comments exist. If, at any point, the database does not return some critical information, the promise errors and it is handled. Promises attempt to retrieve all of that critical information before it starts operating.

Promise.all

A method on the Promise constructor. it accepts an array of promises and resolve all of them or rejects them once a single one of the promises has been first rejected (fail fast).

If all of the promises have been fulfilled, Promise.all is fulfilled with an array of the values from the passed-in promises in the same order as the promises passed in.

Promises do not resolve sequentually, but they are always returned in the order they were promised.

// ES 2015 Promises without Promise.all
function.getMovie(title) {
	return $.getJSON("https://omdbapi.com?t=${title}&apikey=thewdb");
}

var titanicPromise = getMovie("Titanic");
var shrekPromise = getMovie("Shrek");
var braveheartPromise = getMovie("Braveheart");

// ES 2015 Promises with Promise.all

Promise.all([titanicPromise, shrekPromise, braveheartPromise]).then(function(movies) {
	return movies.forEach(function(value) {
		console.log(value.Year); // 1997, 2001, 1995
	}
});

Generators

TOC Shortcut

This is a special kind of function that ES2015 provides. Until recently, once a function was executed, it executed through completion. With the addition of Generators, we can pause and resume execution of a function.

  • Generators allow us to pause/resume function execution
  • They are created with an asterisk * after the function keyword
  • When invoked, a generator object is returned with keys: value and done
  • value is what is returned from the paused function using the yield keyword
  • done is a boolean that returns true when the function completes
function* pauseAndReturnValues(num) {
	for(let i = 0; k < num; i++) {
		yield i;
	}
}

const gen = pauseAndReturnValues(3);

gen.next(); // {value: 0, done: false}
gen.next(); // {value: 1, done: false}
gen.next(); // {value: 2, done: false}
gen.next(); // {value: undefined, done: true}

Yielding Multiple Values

We can place multiple yield keywords inside of a generator function to pause multiple times:

function* printValues() {
	yield "First";
	yield "Second";
	yield "Third";
}

let g = printValues();

g.next().value; // "First"
g.next().value; // "Second"
g.next().value; // "Third"

Iteration

Generators implement a Symbol.iterator so we can iterate over a generator using a for in loop.

function* pauseAndReturnValues(num) {
	for(let i = 0; k < num; i++) {
		yield i;
	}
}

for(val of pauseAndReturnValues(3)) {
	console.log(val);
}

// 0
// 1
// 2

Async Generators

We can use Generators for async operations in ES 2015. ES 2017 introduced some more streamlined means of doing this, but it is still important to be aware of this generator use, as it is fairly common.

function.getMovie(title) {
	console.log("Starting");
	yield $.getJSON("https://omdbapi.com?t=${title}&apikey=thewdb");
	console.log("Ending");
}

const movieGetter = getMovieData("Titanic");
movieGetter.next().value.then(val => console.log(val));

AJAX

AJAX is not a library, framework, or technology. It's just an approach to web development. AJAX allows us to update a web page by sending/requesting data from the server, in the background, without disturbing the current page. This lead to today's single page applications; we no longer need to navigate to different pages. For example, as you scroll, on social media websites, the feed continues to populate. This data did not load up front. It instead waited for a scroll event to determine if the user needed more content and loaded an appropriate amount.

How do we make requests?

  1. XMLHTTP Request
  2. The Fetch API
  3. 3rd Party Libraries: jQuery, Axios, etc.

JSON vs. XML

They are both data formats. We send requests to the server and get more content. That content is returned in some format. APIs do not respond with HTML. They respond with pure data; not structure. XML and JSON are pure data structures that return just the basics. If you consider HTML, in includes a lot of meta and structure data that we do not need. When we make a request, we only want what changed or what we need to add to the page; we already have some idea where information belongs. So, when we get our pure data, we just need to parse and place it.

XML: Extended Markup Language

XML is syntacticly similar to HTML but it does not describe presentation like HTML does. It is a more specific markup.

<pin>
    <title>Adorable Maine Coon</title>
    <author>Michelle K</author>
    <num-saves>1800</num-saves>
</pin>
JSON: JavaScript Object Notation

JSON looks almost exactly like JavaScript objects. JSON is more popular these days because it works so well JavaScript.

'pin': {
    'title': 'Adorable Maine Coon",
    'author': 'Michelle K',
    'num-saves': 1800
}

Making AJAX Requests in Code

XMLHttpRequest

This is the original form of making requests. Despite widespread support it is fairly verbose.

const getQuote = () => {
	const zenXHR = new XMLHttpRequest(); // HTTP is -not- capitalized

	zenXHR.onreadystatechange = function() {
		if (zenXHR.readyState == 4 && zenXHR.status === 200) {
			const quote = document.querySelector('#quoteHeader');
			quote.innerHTML = zenXHR.responseText;
		} else {
			console.log('Error: ' + zenXHR.status);
		}
	};

	zenXHR.open('GET', 'https://api.github.com/zen');
	zenXHR.send();
};

const getDogImage = () => {
	const dogXHR = new XMLHttpRequest(); // HTTP is -not- capitalized

	dogXHR.onreadystatechange = function() {
		if (dogXHR.readyState == 4 && dogXHR.status === 200) {
			const image = document.querySelector('#dogImage');
			const data = JSON.parse(dogXHR.responseText);
			image.setAttribute('src', data.message);
		} else {
			console.log(`Error: ${dogXHR.status}`);
		}
	};

	dogXHR.open('GET', 'https://dog.ceo/api/breeds/image/random');
	dogXHR.send();
};

// LOAD

getQuote();
getDogImage();

// LISTEN

const requestButton = document.querySelector('#requestButton');
document.addEventListener('click', function(e) {
	getQuote();
	getDogImage();
});
Fetch API

Fetch API is not supported by all browsers; most notably, Internet Explorer. However, this will become increasingly common.

const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';

fetch(url)
	.catch((res) => {
		console.log(res); // Bitcoin price information
	})
	.then((error) => {
		console.log(error);
	});

fetch('someFakeUrl.app/login', {
	method: 'POST',
	body: JSON.stringify({
		name: 'Purple',
		username: 'PurpleTheCat'
	})
})
	.catch((res) => {
		console.log(res); // Log Purple in!
	})
	.then((error) => {
		console.log(error); // This is fake, so it will error
	});
jQuery

Having to import jQuery just for the sake of AJAX requests seems excessive but, in lieu of widespread Fetch API support, may be the best, most simplistic alternative if HTTPXmlRequest is too verbose. We can:

  1. Use $.ajax to create a customized request
  2. Use $.getJSON if we know we're requesting JSON
  3. Use $.getJSON.then((data) =&gt; {...}) to use modern Promise syntax instead of the $.ajax success parameter

Here are some examples of this:

// Defaults to GET
$.ajax({
	url: url,
	contentType: "application/json",
	dataType: 'json',
	success: (data) => {
		console.log(data);
	}
});

$.ajax({
	type: "POST",
	url: url,
	data: data,\
	dataType: 'json',
	success: (data) => {
	 console.log("Posted: " + data);
	}
});

// We can even use this bootstrapped function if we know it's JSON:
$.getJSON({
	url: url,
	success: (data) => {
		console.dir(data);
	}
})

// And we can further simplify if we want to use a .then() to handle the response
$.getJSON(url).then((data) => {
	console.dir(data);
});

Async Functions

ES 2017 added some new asynchronous JavaScript functions. These are created using the keyword async. The purpose of asynch functions is to simplify writing asynchronous code; specifically, Promises.

Note: Async functions are not supported in every browser. Check compatability.

async function first() {
	return "We did it!";
}

first(); // Returns a promise

first.then(val => console.log(val)); // "We did it!"

However, the above example is synchronous. We can use the 'await' function to truly make this async friendly.

await

'await' is a reserved keyword that can only be used inside async functions. It pauses the execution of the asynch function and is followed by a Promise. The await keyword waits for the promise to resolve and then resumes the asynch function's execution. Finally, it returns the resolved value. It is similar to a 'pause' button or to the 'yield' in a generator function.

function.getMovie(title) {
	let result = await $.getJSON("https://omdbapi.com?t=${title}&apikey=thewdb");
	return result; // Will not return until 'await' is resolved
}

// We no longer need a .then() because await does that for us.
let movie = getMovie("The Neverending Story");

We can also add async methods to objects:

let movieCollector = {
	data: "The Neverending Story",
	
	async getMovie() {
		let response = await $.getJSON("https://omdbapi.com?t=${this.data}&apikey=thewdb");
		console.log(response);
	}
}

movieCollector.getMovie();

As well as ES 2015 classes:

class MovieData {
	constructor(name) {
		this.name = name;
	}
	
	async getMovie() {
		let response = await $.getJSON("https://omdbapi.com?t=${this.data}&apikey=thewdb");
		console.log(response);
	}
}

let m = new MovieData("The Neverending Story");
m.getMovie();

We can use a try catch block to handle errors:

async function getMovie() {
	try {
		let response = await $.getJSON("https://omdbapi.com?t=${this.data}&apikey=thewdb");
		console.log(response);
	} catch(e) { console.log(`Error: ${e}`); }
}
Making HTTP Requests with Await

When we make two requests sequentually, the second does not begin until the first is resolved:

async function getMovie() {
	try {
		let response1 = await $.getJSON("https://omdbapi.com?t=${this.data}&apikey=thewdb");
		let response2 = await $.getJSON("https://omdbapi.com?t=${this.data}&apikey=thewdb");
		console.log(response1);
		console.log(response2);
	} catch(e) { console.log(`Error: ${e}`); }
}

This defeats the purpose of async. Instead, we can await their resolved promise in parallel instead. This means that, instead of both awaiting the Promise creation and its result, we create the Promise, then only await the result:

async function getMovie(first, second) {
	try {
		let response1 = $.getJSON("https://omdbapi.com?t=${first}&apikey=thewdb");
		let response2 = $.getJSON("https://omdbapi.com?t=${second}&apikey=thewdb");
		
		let data1 = await response1;
		let data2 = await response2;
		
		console.log(data1);
		console.log(data2);
	} catch(e) { console.log(`Error: ${e}`); }
}
Await with Promise.all

We can use Promise.all to await multiple resolved promises.

async function getMovie(first, second) {
	try {
		var results = await Promise.all([
			$.getJSON("https://omdbapi.com?t=${first}&apikey=thewdb");
			$.getJSON("https://omdbapi.com?t=${second}&apikey=thewdb");
		]);
	
		let response1 = results[0];
		let response2 = results[1];
		
		console.log(response1);
		console.log(response2);
		
	} catch(e) { console.log(`Error: ${e}`); }
}

Objects

Objects Overview

TOC Shortcut

Object Function Declarations

TOC Shortcut

In a non-class setting, instead of colon syntax, we can now declare functions inside objects as we would in other languages:

// ES5
const Person = {
	sayHi: function() {
		console.log("Hi!");
	}
}

// ES 2015
const Person = {
	sayHi() {
		console.log("Hi!");
	}
}

Computed Property Names

TOC Shortcut

We can now add property names while defining our object.

// ES5
const name = "Brandon";
const Person = {};
Person[name] = name;

// ES 2015
const Person = {
	[name]: "Brandon"
}

Native JavaScript Prototypes

TOC Shortcut

The prototype object is a base template that can be used to create new objects. We can also modify or extend the prototype to have additional functionality, though this is less common.

Remember, the new keyword creates objects from constructor functions:

let m1 = new Map() // Valid
let m2 = Map() // Type Error

When we create a 'new' object:

  1. It creates an object out of thin air
  2. It sets the value of 'this' to be that object
  3. It adds a 'return this' to the end of the function
  4. It creates a link, accessible by __proto__, between the object created and the prototype property of the constructor function

Anatomy of Prototype

Every constructor function has a property on it called 'prototype'. This is an object.

The prototype object has a property on it called 'constructor' that points back to the Constructor function.

Any time an object is created using the 'new' keyword, a property called '__proto__' gets created, linking the object and the prototype property of the constructor function.

This means that every 'new' object gets the same functionality as the prototype; it serves as a template for our objects.

 ________                        ____________
(fn/class) --> .prototype   --> [ Person obj ]
( Person ) <-- .constructor <-- [ .prototype ]
 (______)                       [____________]
                                 /.__proto__\
                                /            \
                              obj1          obj2

Example

These are the two ways of defining an Object in JavaScript. The latter, with class syntax, is more modern. Regardless, accessing both oldPerson.prototype and Person.prototype yield a return value despite no explicit declaration; we get them for free.

Once we create a new Person, that new instance of Person gets its own property, __proto__, linking back to class Person's 'prototype' property:

function oldPerson(name) {
  this.name = name;
}

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

oldPerson.prototype;
Person.prototype;

let p = new Person("Brandon");

p.__proto__;

p.__proto__ === Person.prototype; // True

Person.prototype.constructor === Person; // True

Prototype Object

Because the class property 'prototype' is shared among all objects created by a constructor function we can add functionality after the class has been defined.

Person.prototype.isAwesome = true

brandon.isAwesome // True, thanks to __proto__

proto (dunder proto)

When we call a function or property on and object, and that object does not specifically have it, it then checks the dunder proto to see if it lives there. If it doesn't find it there, it checks the next dunder proto, and so on, until it is found. This is because many objects are extended from other objects; they inherit all of those parent object functions and properties.

Curious about an Object and want to see its full functionality? Call the __proto__ property on it. If we use it on Array, we can see all of it's methods.

Adding Methods to the Prototype

In older JavaScript "class-like" declarations we can leverage prototype to create new functions and properties available to every instance of that object.

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
    return this.name;
}

Prototypal Inheritence

Even if we are not using modern class syntax with 'extend' we can still pass methods and properties from one class to another.

Naive Example
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

function Student(firstName, lastName) {
    return Person.apply(this, arguments);
}

Student.prototype = Person.prototype;
Student.prototype.status = function() {
    return "I am a student!"
}

let p = new Person("Brandon");
p.status; // "I am a student!", but that's not right!

However, this is not enough. If we create a new Person, we are still able to access Student's functions. This is because the prototypes are linked. Instead, we need to create a brand new object.

Complete Example

We can use Object.create to accomplish this. It creates a brand new function. The first paramater is what the prototype object should be for the newly created object.

We do not use 'new'. It does approximately the same thing but also adds additional, unnecessary properties on to the prototype object.

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

function Student(firstName, lastName) {
    return Person.apply(this, arguments);
}

Student.prototype = new Person; // Incorrect
Student.prototype = Object.create(Person.prototype); // Correct

Destructuring

TOC Shortcut

We use destructuring to extract values from data stored in objects and arrays.

Destructuring Objects

Previously, we would have to specifically retrieve a key from an object and assign it to a variable.

// ES5
const person = {
	name: "Brandon",
	email: "foo@bar.com"
}

let name = person.name;

name; // Brandon

Now, we can directly unpack specific properties from objects. The first example requires that the variable name match the key name. However, in the second example, we can use colon syntax to create our own names.

// ES 2015
let {name} = person;
let {name:personsName, email:personsEmail} = person;

name; // Brandon
personsName; // Brandon
personsEmail; // foo@bar.com

Destructuring Arrays

Similar to objects, we can use destructuring to interact with arrays as well. Consider this value assignment:

// ES5
const arr = [1, 2, 3];
let a = arr[0];
let b = arr[1];
let c = arr[2];

Now, we can do this in one line:

// ES 2015
const arr = [1, 2, 3];
let [a, b, c] = arr;  // a = 1, b = 2, c = 3

Consider these return value assignments:

// ES5
function returnNumbers(a, b) {
	return [a, b];
}

let first = returnNumbers(5, 10)[0]; // 5
let second = returnNumbers(5, 10)[1]; // 10

We can refactor and minimize the return value assignment footprint, too.

// ES 2015
[first, second] = returnNumbers(5, 10);

We can also swap values:

const swap = (a, b) => {
	return [a, b] = [b, a];
}

const swap = (arr, index1, index2) => {
	return [arr[index1], arr[index2]] = [arr[index1], arr[index2]];
}

Object.assign

Making copies of objects in JavaScript is not as easy as assigning one to another, because that is merely assigning a reference. We can use Object.assign to resolve this.

// Broken ES5 example
let o = {name: "Brandon"};
let o2 = o;
o2.name = "Jenny";
o.name; // Jenny

Using Object.assign fixes this. It creates copies of objects without the same reference.

Note: If the first parameter is not an empty object, the new object will maintain a reference to the source object. Make sure to pass empty object {} first before the second parameter: The object you would like to copy.

// ES 2015
let o = {name: "Brandon"};
let o2 = Object.assign({}, o);
o2.name = "Jenny";
o.name // Brandon
o2.name // Jenny

This does not create a deep clone. If there are objects inside of the objects that you are copying, they will still maintain references to the original object. You may need to use a different data structure or just instantiating a new instance of an object instead.

Hash Maps

TOC Shortcut

Maps are JavaScript's hash map. Maps may only have string keys, but their values may be anything. Prior to ES2015, objects were replacements for maps. They are similar to objects except that the keys can be any data type; not just strings.

Unlike Object.prototype, you cannot accidentally overwrite keys.

const m = new Map();

When to use a Map

  • If you need to look up keys d ynamically
  • If you need non-string keys
  • If you are frequently adding/removing key-value pairs
  • If you are operating on multiple keys simultaneously

WeakMap

WeakMap serves as a performant alternative to a Map. However, all of the keys must be objects and not primitives. WeakMaps cannot be iterated over. WeakMaps are less common.

Iteration

Maps implement a Symbol.iterator. This means we can use a for of loop. See the Map keys and values methods for more.

Methods

set

Sets a key-value pair in the Map.

const m = new Map();
m.set("Key", "Value");
m.set(123, 456);
m.set("isNice", true);
delete

Deletes a key-value pair, by key name, from the Map.

const m = new Map();
// ...
m.delete(someKey);
get

Gets a key-value pair in the Map. We can use this in tandem with Set to update a key-value pair.

const m = new Map();
// ...
m.get(key, value);
has

Determines if the Map contains some key.

const m = new Map();
// ...
m.has(someKey);
keys

This is a MapIterator of all keys in the map. This allows us to use a for of loop.

const m = new Map();
// ...
m.keys() // returns all keys
values

This is a MapIterator of all values in the map. This allows us to use a for of loop.

const m = new Map();
// ...
m.values() // returns all values
size
const m = new Map();
// ...
m.size // n size by number of key-value pairs

Sets

A data structure in which all values are unique. They exist in a few other languages but are new to JavaScript with ES 2015.

  • Any type of value can exist in a set
  • They are created using the new keyword

Here is a set implementation. Methods are described after the snippet:

const s1 = new Set;
const s2 = new Set([3, 1, 4, 5]); // Can be instantiated with an array

const s = new Set;
s.add(10);
s.add(20);
s.add(10); // Won't be added since it is already included
s.size; // 2

WeakSet

Much like Maps and WeakMaps, there is a performant WeakSet option. WeakSet only accepts objects as values.

Iteration

Sets implement a Symbol.iterator so we can iterate over them using a for of loop.

Methods

add

Adds a specific value to a set.

delete

Deletes a specific value from a set.

has

Detects if a set has some value.

size

Returns the size of a set.

Object Rest

ES 2015 introduced the rest operator. Now, we can use rest to gather the remaining keys (the "rest" of the keys) and values in an object and create a new one out of them:

let person = {first: "Brandon", last: "K", job: "Software Engineer", pet: "Purple"};
let {first, last, ...data} = person;
first; // Brandon
last; // K
data; // {job: "Software Engineer", pet: "Purple"}

Object Spread

ES 2015 introduced the spread operator. Now, can use spread on objects to spread out keys and values from one object to another. It can be used for creating objects starting with default values and is a more concise alternative to Object.assign.

Note: This is very common in React and Redux.

let person = {first: "Brandon", last: "K", job: "Software Engineer", pet: "Purple"};
let person2 = {...person, first: "Jenny", last: "X", job: "OMBUDS"};

person2.first; // Jenny
person2.last; // X
person2.job; // OMBUDS
person2.pet; // Purple

Arrays

Arrays Overview

TOC Shortcut

This section outlines some of new additions to arrays, as well as recaps existing functionality for the sake of comprehension.

from

TOC Shortcut

Allows us to convert an array-like object into an array.

// ES5
var divs = document.getelementsByTagName("div"); // returns an array-like object
divs.reduce; // undefined, since it is not an actual array

var converted = [].slice.call(divs); // Convert into an array
converted.reduce // function reduce() { ... }

Instead of the array slice call syntax above, we can now use Array.from():

// ES 2015
let divs = document.getElementsByTagName("div");
let converted = Array.from(divs); // An array of divs with full array functionality!

push

TOC Shortcut

Adds a value to the end of the array.

arr.push(3)

pop

TOC Shortcut

Removes the value at the end of the array.

arr.pop()

shift

TOC Shortcut

Removes a value from the beginning of the array.

arr.shift()

unshift

TOC Shortcut

Adds a value to the beginning of the array.

arr.unshift(3)

for Each Loop

TOC Shortcut

  1. Iterates through an array
  2. Runs a callback function each value in the array
  3. When the loop ends, forEach returns 'undefined'

Anatomy of forEach

forEach is called on the array and accepts an anonymous function with three potential arguments:

  1. value: The current value
  2. index: The current value's index in this array
  3. array: The entire array
[1, 2, 3].forEach( (value, index, array) => {
 // ...
});

for in Loop

TOC Shortcut

Instead of a standard for loop, we can use a 'for in' loop to streamline writing it. It is traditionally used to loop over keys in an object.

for(let i = 0; i < someArray.length; i++) { let index = i }

// Instead, we can:

for(let i in someArray) { i = 0, 1, ... n }

for of Loop

TOC Shortcut

Taking the 'For In' a step further, 'for of' will refer directly to the value in the array. We no longer have to worry about keys or indices with a 'for of' loop.

for(let i = 0; i < someArray.length; i++) { let value = someArray[i] }

// Instead, we can:

for(let value of someArray) { i = 50, i = 73, ... i = nth value }

map

TOC Shortcut

forEach always returns undefined, but very commonly, we want to transform one array into another array with different values. We could use a forEach and push values into a new array or we could use map.

Once invoked on an array:

  1. It creates a new array
  2. It iterates over the array it was called on and adds the result of that callback function to the new array
  3. It returns a brand new array

Map returns a new array of the same length that it was invoked on.

const arr = [1, 2, 3];

const doubleArray = (arr) => {
    return arr.map((value, index, array) => {
        return value * 2
    });
}

doubleArray(arr); // Returns [2, 4, 6]

filter

TOC Shortcut

Just like map and forEach it accepts a callback function. However, the result of the callback function is a boolean.

  1. Creates a new array
  2. Iterates through an array
  3. runs a callback function on each value in the array
  4. If the result of the callback returns true that value will be added to the new array
  5. Otherwise, it will be ignored
const arr = [1, 2, 3];

const filterEven = (arr) => {
    return arr.filter((value, index, array) => {
        return value % 2 === 0
    });
}

filterEven(arr); // Returns [2]

some

TOC Shortcut

  1. Iterates through an array
  2. Runs a callback on each value in the array
  3. If the callback returns true for at least one single value it returns true
  4. Otherwise, it returns false
const arr = [1, 2, 3];

const filterEven = (arr) => {
    return arr.some((value, index, array) => {
        return value % 2 === 0;
    });
}

filterEven(arr); // Returns True because the array has an even number

every

TOC Shortcut

  1. Iterates through an array
  2. Runs a callback on each value in the array
  3. If the callback returns true if every item in the array meets the criteria
  4. Otherwise, it returns false
const arr = [1, 3, 5];

const filterEven = (arr) => {
    return arr.every((value, index, array) => {
        return value % 2 === 0;
    });
}

filterEven(arr); // Returns True because all the numbers are odd

reduce

TOC Shortcut

Reduce allows us to take an array and turn it into another data structure such as a single integer value, a string, or an object. It accepts a callback function and an optional second parameter.

The first parameter to the callback is either the first value in the array or the optional parameter. The first parameter to the callback is often called the accumulator.

  1. Iterates through an array
  2. Runs a callback on each value in the array
  3. The returned value from the callback becomes the new value of accumulator

Whatever is returned from the callback function becomes the new value of the accumulator.

Anatomy of Reduce

[1, 2, 3].reduce(function(accumulator, nextValue, index, array) {

/* Whatever is returned in here will be the value   
 * of the accumulator in the next iteration
 */

}, optional second parameter)
Accumulator Only
const arr = [1, 2, 3, 4, 5]

let result = arr.reduce((accumulator, nextValue) => {
    return accumulator + nextValue
});

console.log(result); // The sum of the array: 15
Accumulator and Optional Callback
const arr = [1, 2, 3, 4, 5]

const result = arr.reduce((accumulator, nextValue) => {
    return accumulator + nextValue
}, 10);

console.log(result); // The sum of the array,
                     // but the accumulator starts at 10,
                     // So the result is 25 (15 + the 10 starter)
Strings
const arr = ["Brandon"]

const result = arr.reduce((accumulator, nextValue) => {
    return accumulator + nextValue
}, "This guide was brought to you by ");

console.log(result); // "This guide was brought to you by Brandon"
Objects
const arr = [5, 4, 1, 4, 5];

arr.reduce((accumulator, nextValue) => {
    if(nextvalue in accumulator) {
        accumulator[nextValue]++;
    } else {
        accumulator[nextValue] = 1
    }
    
    return accumulator;
}, {});

/* Returns 
    {
        5: 2,
        4: 2,
        1: 1
    }
*/

find

It returns the value found or undefined if it is not found. Find accepts a callback function with value, index, and array; similar to forEach, map, filter, etc.

const people = [{name: "Brandon"}, {name: "Jenny"}];

people.find((value) => {
	return value.name === "Brandon"; // "Brandon" || undefined
});

findIndex

Finds and returns the index, or, if no match, -1, of a matching item.

const people = [{name: "Brandon"}, {name: "Jenny"}];

people.findIndex((value) => {
	return value.name === "Brandon"; // 0
});

includes

Returns a boolean value on whether or not an array contains some value.

const people = [{name: "Brandon"}, {name: "Jenny"}];

people.includes((value) => {
	return value.name === "Brandon"; // True
});

Numbers

Numbers Overview

TOC Shortcut

Features methods related to the Number object in JavaScript.

isFinite

Checking to see if a number is not a number is difficult. Now, we can use Number.isFinite to check whether a number is a number and is not 'NaN':

function seeIfNumber(val) {
	if(Number.isFinite(val)) {
		return "It is a number!";
	}
}

Strings

Strings Overview

TOC Shortcut

This section is dedicated to newer string methods.

String Templating

Using string templating we can embed variables in our text using double ticks, dollar sign $, and curly braces {}.

let name = "Brandon";
console.log(`My name is ${name}`;

padStart

Allows us to pad the start of the string. padStart accepts two arguments:

  1. The total length of the new string
  2. What to pad with from the start. Defaults to empty space

This is useful when we want to make sure a set of strings are all the same length.

padEnd

Similar to padStart, except that it allows us to pad the end of a string.


Classes in JavaScript

Classes in JavaScript Overview

TOC Shortcut

ES2015 introduced the class keyword and class syntax similar to other languages including, but not limited to:

  1. class declarations
  2. class constructors
  3. class helper method declarations
  4. Inheritence with Extends
  5. The 'super' keyword to access parent functionality

class Keyword

TOC Shortcut

A class is a blueprint for creating objects with pre-defined properties and methods. While it is a staple of object oriented programming, JavaScript does not have built-in support for object oriented programming. The 'class' keyword is a new way to interface with JavaScript's object prototype.

  • It is an abstraction of constructor functions and prototypes
  • The 'class' keyword creates a constant
  • The 'class' keyword does not hoist; make sure it is at the top
  • We use 'new' to create instances of class objects; else you will receive a type error

The constructor keyword is used to create requirements for the creation of a new instance of this object. Without this information, an instance of this object cannot be created. Classes can have multiple constructors.

Constructor, Instance, and Static Instance Methods

Constructor Methods

Constructor methods are used to instantiate an instance of this class. We may have multiple constructors to accommodate different input. In some cases, we may have less information and use default values to fill in the missing information. In others, we may have more complete information, and instantiate with the summation of that.

Instance Methods

Standard methods are declared without any additional keywords. They are used to interact with 'this' instance of some object. Getters and Setters are examples of instance-specific methods.

Static Instance Methods

The static keyword is used to create methods that do not require an instantiated object. This is useful for helper methods that have to do with the object but do not necessarily require specific object information. They allow us to interface with objects of this type rather than one specific object. For example, the Student class below may have a static method that serves as a Student factory. Or, in this case, emails all students.

Note: Static functions lead with an uppercase unlike their object specific counterparts.

In ES5 a class-like declaration would look like this:

// ES5

function Student(firstName, lastName) {
	this.firstName = firstname;
	this.lastName = lastName;
}

Student.prototype.getName = function() {
	return this.firstName + " " + this.LastName;
}

var brandon = new Student("Brandon", "K");
brandon.getName() // Brandon K

Now, we can use class syntax to accomplish similar functionality:

// ES 2015
class Student {
    constructor(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
        this.classesPassed = 0
    }
    getFullName() {
        return "`Your full name is ${this.firstName} ${this.lastName}`"
    }
    passedClass() {
        this.classesPassed++
        return "`You have passed ${this.classesPassed} class(es)!`"
    }
    
    static EnrollStudents(...students) { // Does not pertain to one particular student
        // Email students that were passed
        // (Note: We use the spread operator to indicate that there may be an unknown number of students)
        // (See the spread operator section for more)
    }
}

let s = new Student("Brandon", "K")
s.firstName // "Brandon"
s.lastName // "K"
s.getFullName // "Your full name is Brandon K"
s.passedClass() // "You have passed 1 class(es)!"

// Imagine I instantiate a few new students here.
// We can call EnrollStudents, a static method, directly, without relying on other students.

Student.EnrollStudents(student1, student2, student3)

Inheritance

TOC Shortcut

We use inheritance to pass along methods and properties from one class to another.

For example, we may have class Pet, which has some generic properties such as name, owner, veteriarian, favorite toy, and favorite food. However, despite sharing some similarities, cats and dogs may have additional properties that make them more unique such as number of naps, number of times played fetch, and more.

We can abstract away some of the common property work using inheritance:

      -> Cat
Pet -|
	  -> Dog

Here, Cat and Dog are derived from Pet; they inherit all of the properties of Pet and include some of their own.

They may both have a speak() method but Cats and Dogs certainly do not sound the same. We an create a speak() method in Pet, as a template, but not define it until Cat and Dog have a defined "sound" property. Pet, on its own, may have an undefined sound property, as a Pet is more abstract than our concrete Cat and Dog derived, child classes.

// ES5 Inheritance
function Pet(name, owner) {
	this.name = name;
	this.owner = owner;
	this.sound = undefined;
}

Pet.prototype.speak() = function() {
	return this.sound; // undefined
}

function Cat(sound) {
	Person.apply(this, arguments); // Useful for many arguments
	this.sound = sound;
}

Now, we can use 'class' and 'extends' to accomplish inheritance:

// ES 2015
class Pet {
	constructor(name, owner) {
		this.name = name;
		this.owner = owner;
		this.sound = undefined;
	}
	
	speak() {
		return this.sound; // undefined
	}
}

class Cat extends Pet {
	constructor(name, owner, sound) {
		this.name = name;
		this.owner = owner;
		this.sound = sound; // Meow
	}
	
	// speak() is inherited
}

class Dog extends Pet {
	constructor(name, owner, sound) {
		this.name = name;
		this.owner = owner;
		this.sound = sound; // Woof
	}
	// speak() is inherited
}

We can dry this up even more using the 'super' keyword.

Super

We can use the 'super' keyword to access parent methods. It invokes a method by the same name in the parent.

class Pet {
	constructor(name, owner) {
		this.name = name;
		this.owner = owner;
		this.sound = undefined;
	}
	
	speak() {
		return this.sound; // undefined
	}
}

class Cat extends Pet {
	constructor(name, owner, sound) {
		super(name, owner);
		this.sound = sound;
	}
}

Methods

Methods Overview

TOC Shortcut

Default Parameters

We can now use default parameters in JavaScript. If an argument is missing, we can default to certain values:

const add = (a = 0, b = 0) => {
    return a + b
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment