Check out the new web development bootcamp! learn more

It has already been one month since I first created this blog! I am trying to improve it. Do you have any ideas? leave a feedback

home > learn javascript part3

Web Dev Bootcamp ∙ Learn Javascript Part 2 ∙ ES6

Hieu Nguyen · May 05, 2020 · 10 min read

webdevjavascriptcourse

0

0 leave some love!

Goals

  • understand what ES6 is
  • Learn the most common ES6 features

What is ES6?

ES6 is the next version of Javascript following ES5. It was released in 2011 an introduced many useful changes for developers

ES6 Features

Let keyword

let is an alternative to var that allows you to scope a variable to a block as opposed to the global/function scope of a var

function fn() {
  var globalVar = 2
  if (true) {
    let blockVar = 3 // can only be accessed inside this if block
    console.log(globalVar, blockVar)
  }
  console.log(globalVar)
  console.log(blockVar) // will produce an error because blockVar is not been defined in the function scope
}

fn() // will generate error

undefined

const keyword

the const keyword lets us create variables that can only be assigned once and can not be changed

const myConst = "Dogs are great!"

myConst = "no they are not" // will generate an error because we are trying to change a constant

constant

Arrow functions

Arrow functions are used to simplify how we write functions and to make them more concise

//traditional function

function double(num) {
  return num * 2
}

double(2) // 4

//rewritten using arrow function

var double = num => num * 2

double(2) // 4

Arrow functions are denoted by =>. On the left side we have the parameters and on the right side be have the function definition

There are several caveat we have to be aware of when using arrow functions

  1. Multiple parameters require a () ex.
const singlePara = param1 => console.log(param1)

const multiplePara = (param1, param2) => console.log(param1, param2)
  1. Function definitions that do not use {} are implicitly saying ‘return whatever is in the definition’

ex.

const double = num => num * 2 // since we are not using {} to encase the function definition, we are saying do num*2 and return it
const doSomething = () => ({
  name: "charlie",
  type: "dog",
}) // Not using {}, but since we are returning something that spans multiple line we are using ()
// Inside the (), we have an object {name : 'charlie', type: 'dog'} which will be returned

const fn = () => {
  // some code here

  return result
} // Now we are using {} to encase the function definition, so we have to add a return statement if we want to return something
  1. We can also pass arrow functions to other function
var arr = [1, 2, 3]
// passing function using anonymous function
arr.map(function(currentElement) {
  return currentElement * 2
})
// the new way of doing things...
arr.map(currentElement => currentElement * 2)

classes

classes are part of OOP(object orientated programming) paradigm. This design insists that data should be represented as human readable objects or classes

Let’s learn how to create classes in Javascript

Declaring a class

class MyClass {}

Member variables and functions

class MyClass {
  name
  age
  constructor(n, a) {
    this.name = n
    this.age = a
  }

  toString() {
    return `name: ${this.name} age: ${this.age}`
  }
}

Here we have two public member variables name and age

We are initializing these members using the constructor. Each class can only have one constructor.

The purpose of the constructor is to initialized our object. Here we are assigning our name and age

We have a member function called toString() which prints the name and age of our object

Creating an object from class

var obj = new MyClass("devsurvival", 1) // we use the new keyword to create a new object and pass in the name and the age for the constructor
console.log(obj.toString()) // accessing the member function toString()
console.log(obj.name) // getting the name
obj.age = 2 // setting age

Setters/Getters and Private Members

There is a rule of thumb in programming that states that all member variables should be private and they can only be modified or accessed via setters and getters.

class MyClass {
  #name
  #age
  constructor(name, age) {
    this.#name = name
    this.#age = age
  }
  set name(n) {
    this.#name = n
  }
  get name() {
    return `name: ${this.#name}`
  }

  #myPrivateFunction() {
    console.log(
      "this is private and can only be accessed inside the class body"
    )
  }
}

var obj = new MyClass("devsurvival", 2)
obj.name = "charlie" // using setter to set the name
console.log(obj.name) // using getter to get the name
obj.#age = 2 // will generate an error
obj.myPrivateFunction() // will generate an error

Default

defaults allows to optionally pass in a parameter

function fn(value = 0) {
  console.log(value)
}

fn() // 0
fn(1) //1

For of loop

allows you to loop over the elements on an array without explicitly knowing the length of the array

var arr = [1, 2, 3]
for (let v of arr) {
  console.log(v)
}

// prints 1, 2,3

For in loop

looping through the keys of an object

var object = { name: "devsurvival", age: 2 }

for (let key in object) {
  console.log(object[key])
}

Tempate Strings

You can embedded variable inside of strings without using messy string concatenation

//traditional Javascript

var str = "hello " + name + "You wanted " + donuts + " donuts"

// ES5

var str = `hello ${name}. You wanted ${donuts} donuts`

variables used in template string are encased in a ${}

Destructuring

destructing allows you parse an object and get certain keys

let’s say you want to get the key: ‘name’ from the object, traditionally you would do obj.name each time you want to access the name

Wouldn’t it be better to parse the name once and use it? That is what destructuring allows you to do

var obj = { name: "devsurvival", age: 2 }

const { name } = obj

You can also provide an alias for a key!

var obj = { name: "devsurvival", age: 2 }

const { name: nameAlias } = obj
console.log(nameAlias) // nameAlias is an alias for the name key

Spread Operator

The spread operator is used to spread an array or an object. Let me explain using an example

var arr1 = [1, 2, 3]
var arr2 = [4, 5, 6]
// let's say we want to append arr2 to arr1
arr2.forEach(currentElement => arr1.push(currentElement))

// using spread operator, we can right this more concisely

var arr1 = [...arr1, ...arr2] // we are spreading the values of each array into a new array

How About Objects?

you can also spread objects!

function addKeys(obj1, obj2) {
  return { ...obj1, ...obj2 }
}
var obj = { name: "devsurvival" }
var obj2 = { age: 2, favoriteFood: "donut" }
addKeys(obj, obj2)

here we created a function addKeys that takes the key value pairs in the second object and spread them into the first object

Rest operator

The rest operators is kinda like spread counter part. Instead of spreading things, it gathers them!

let’s see some use cases

we can use rest operators in functions to pass a variable number of parameters

function fn(firstParam, ...otherParams) {
  console.log(firstParam)
  console.log(otherParams)
}

fn(1, 2, 3, 4)

// prints 1
// prints [2,3,4]

It can also be used to gather the keys in an object

const object = { name: "devsurvival", age: 3, food: "chicken nuggets" }

const { food, ...restOfTheKeys } = object

// food = 'chicken nuggets'
// restOfTheKeys = {name: "devsurvival", age: 3}

import and export

importing and exporting are how we share codes across multiple files

// file1.js

//named export
export function fn() {
  console.log("function from file1")
}

// file2.js

import { fn } from "file1.js"

fn()

default export

// file1.js

export default function() {
  console.log("another function from file 1")
}

// file2.js
import fn from "file1.js"

fn()

Sets and Map

Sets

are just a list of unique things. A set can contain numbers and strings

var set = new Set()
set.add(1)
set.add("one")
set.add(1) // will not be added again since 1 already exists in the set

// set = [1, "one"]

Map

Maps are like dictionaries that allows you to search for a key and get the value based on the key

var map = new Map()
const key = "name"
map.set(key, "devsurvival")
map.get(key) // name: 'devsurvival'

setTimeout and setInterval

setTimeout allows you to execute some code after a specified amount of milliseconds has passed

const timeoutId = setTimeOut(() => {
  console.log("one second has passed!")
}, 1000)

// it will print out the message after one second has passed

What if something happens and we want to cancel the setTimeout?

const timeoutId = setTimeOut(() => console.log("one second has passed!"), 1000)

clearTimeout(timeoutId) // each setTimeout() returns an id of the timeout which is used for clearing/cancelling the timeout

setInterval is used to perform some operation with a given time interval

const setIntervalId = setInterval(
  () => console.log("another second has passed"),
  1000
)

// this will print the message every second

Promises

promises are asynchronous operations meaning they do not block the program execution after being invoked.

This is commonly used when fetching data from some external source which is unpredictable in how long it takes to respond. It could be instantaneous, 2 seconds, or a couple of hours. We just don’t know

console.log("doing something...")
console.log("fetched data: ", fetchData()) // this is asynchronous
console.log("finished doing something...")

//output:

// "doing something..."
// "finished doing something..."
// "fetched data: ..."

In the program above, Javascript will not wait for fetching the data to complete before printing ‘finished doing something…’

How do we wait to get a response before proceeding?

console.log("doing something...")
fetchData()
  .then(data => {
    console.log("fetched data: ", data)
    console.log("finished doing something...")
  })
  .catch(error => {
    console.log("there was an error :( ", error)
  })

We use .then() and .catch(). .then() will wait for fetching data to complete and pass the result into the data parameter

.catch() will catch any error that is returned

Creating a Promise

var newProm = new Promise((resolve, reject) => {
  // do something ...
  if (2 === 2) {
    resolve(2)
    // if error
    reject("sorry")
  }
})

newProm.then(res => console.log(res)) // prints 2

Async and Await

What happens when we have a series of request that depends of the result of the previous request

using .then()

function makeRequests() {
  request1().then(result => {
    request2(result).then(result => {
      request3(result).then(result => {
        request(4).then(result => {
          //...
        })
      })
    })
  })
}

this nesting is hard to read and harder to debug.

To avoid this, they introduced Async and Await

Same implementation using Async and Await

async function makeRequests() {
  const result1 = await request1()
  const result2 = await request2(result1)
  const result3 = await request3(result2)
  const result4 = await request4(result3)
}

To use await to wait for our requests, we have to mark the function with async

Cool right? But there are some caveats when using async functions:

  1. All functions marked with async returns a promise
  2. Don’t stick an await in front of every promises! If you don’t really care about the return value, just let them be asynchronous.

Error Handling

How do we handle errors?

try {
  // do something that can cause an error
  // let's see.. I know! let's reference a variable that doesn't exists!'
  value / 0
} catch (err) {
  console.log(err)
}

What’s Next?

Start learning bash commands


Hieu Nguyen - Founder of devsurvival.com. I am also a video gamer, a full stack developer, and an anime lover. I'm also an advocate of teaching others how to code.
Archive