Modern Javascript: Everything You’ve Missed Out In The Last 10 Years

JavaScript has come a long way since I knew it as the letter “D” in DHTML. For anyone like me who didn’t want to use the latest syntax that might require polyfills or a transpiler, I wrote this cheat sheet to get you familiar with all the benefits that are widely supported in modern browsers.

Content

  • Array functions
  • const / let
  • Nullish coalescing ?? and Optional chaining? .. operators
  • Async / Await
  • Arrow functions () => {}
  • for … of
  • for await … of
  • Classes
  • get / set
  • function default parameters
  • function named parameters
  • function rest … parameter
  • Destructuring
  • Shorthand functions aka Methods
  • Promise.all
  • Template literals
  • Proxy
  • Module import / export
  • Read more

Array functions

Check out all of these new built-in array functions. No need for underscore or lodash anymore.

Array.every ()
Array.filter ()
Array.find ()
Array.findIndex ()
Array.forEach ()
Array.from ()
Array.includes ()
Array.isArray ()
Array.lastIndexOf ()
Array.map ()
Array.reduce ()
Array.reduceRight ()
Array.some ()

Array docs

const / let

These new keywords declare variables in block scope (as opposed to global or functional scope). Using const implies that the value will not change since the link is unchanged. Use letif the value changes.

Let documentation

?? and?.

?? checks if a value is null or undefined. No need to use anymore !!

?. checks if a value is true before calling the next property or function. Extremely useful when dealing with additional props.

Optional chaining documentation

let a, b=1
let result = a ?? b
print(result)

result = (a !== null && a !== undefined) ? a : b;
print(result)
      
print({x:1}?.a?.b ?? "not found")
      

Async / Await

The async / await keywords are there to save you from callback hell. Use awaitto make an asynchronous call look like a synchronous call, i.e. performance await fetchUserName() will not advance to the next line until fetchUserName () has completed. Please note: to use await, you have to execute a function declared as async, i.e. async function fn () {await fetchUserName ()}.

Async / Await docs.

function fetchUserName() {
  return new Promise(resolve => setTimeout(resolve, 500))
}
      
async function withAsync() {
  print("withAsync: fetching...")
  await fetchUserName()
  print("withAsync: done")
}
await withAsync()
      
function withoutAsync() {
  print("withoutAsync: fetching...")
  fetchUserName().then(()=>print("withoutAsync done"))
}
withoutAsync()
    

Arrow functions () => {}

These are functions tied to the current context. There are three main species that you will see in the wild:
one argument, one line, many lines.

A single-argument form does not require parentheses, and a single-line form does not require an operator return; return unconditional.

1 const fn = a => a*2

One argument. One line.

Multi-line form requires operator returnif the function intends to return something. Multiple arguments require parentheses.

const fn = (a,b) => {
  console.log(a,b)
  return a*b
}

Multiple arguments, multiple lines.

Arrow function docs

for … of

Used to iterate over an iterator. Likewise for...in, except that you don’t need to check hasOwnProperty… You cannot use this looping syntax on an object directly because the object does not have an iterator. Use instead Object.entries ({}) to get the iteration.

for … of docs

const x = {a: 1, b: 2}
for (const [key, value] of Object.entries(x)) {
  print(`${key}=${value}`)
}

for await … of

Asynchronous iteration was introduced in 2018. As well as Promise.all, it can be used to synchronize many asynchronous tasks. The example below shows 3 tasks running asynchronously. The loop processes one result at a time, in order; in this case, the fastest tasks to complete are evident only at the end of the iteration.

for await … of docs

const delay = (n) => {
  return new Promise((resolve) => {
    setTimeout(()=>{
      print("resolve "+n)
      resolve(n)
    }, n)
  })
}

const delays = [
  delay(150),
  delay(50),
  delay(25)
]

for await (const ret of delays) {
  print("for loop await "+ret)
}

Classes

In 2015 ES6 ported classes to Javascript. Javascript classes are like classes from other languages ​​that you know and love. Inheritance, class methods, getters and setters, properties, etc.

Class documentation

class A {
  constructor(name) {
    this.name = name
  }
  myProp = "myProp"
  static foo() {
    print("Static method says foo")
  }
}
class B extends A {
  constructor(name, age) {
    super(name)
    this.age = age
  }
  toString() {
    return `${this.name} ${this.age}`
  }
}
A.foo()
const b = new B("Catch", 22)
print(b)
print(b.myProp)

get / set

Get and set are functions called properties, for example person.age = 16; person.age> 18… This comes in very handy when you need a dynamic or computed property. And they can be used with both classes and regular objects.

get / set documentation

Classes with getters and setters

class A {
  constructor() {
    this._firstName = "Jane"
    this._lastName = "Smith"
  }
  get fullName() {
    return `${this._firstName} ${this._lastName}`
  }
  set firstName(v) {
    this._firstName = v
  }
}
const a = new A()
print(a.fullName)
a.firstName = "John"
print(a.fullName)

Objects with getters and setters

const x = {
  get now() { return new Date() }
}
print(x.now)

function default parameters

Hooray! Now you can specify default parameters in your function definition. Works as you would expect.

Default parameter docs

function greet(msg="Hello world") {
  print(msg)
}
greet()
greet("hi")

function named parameters

Using the magic of destroying objects, functions can now have named parameters.

Named parameter docs

function greet({name = "Jane", age = 42} = {}){
  print(name + " " +age)
}
greet()
greet({name: "John", age: 21})

function rest … parameter

The reset parameter allows the function to accept an arbitrary number of arguments as an array. It is recommended to use this instead arguments

Rest parameter docs

function greet(msg1, ...msgs) {
  print(msg1)
  msgs.forEach(s => print(s))
}
greet("hi", "hello", "world")

Object.assign and spread operator

Object.assign(target, source) combines two or more objects into one. It modifies the target in place, so if you prefer to create a new object, pass an empty object literal as the first argument.

Alternatively, you can use the spread operator ... to combine multiple objects: {... obj1, ... obj2}though be aware that spread will not call setters on an object, so to be most portable consider Object.assign… The spread operator can also be used with arrays, as shown in the last code example.

Spread syntax docs

const source = {x: 1, y: 4}
const target = Object.assign({}, source)
print(JSON.stringify(target))

const spread = {a: 1, b: 2, ...source}
print(JSON.stringify(spread))

const ary1 = [1]
const ary = [...ary1, [2,3]]
print(ary)
      

Destructuring

Destructuring allows you to retrieve values ​​from objects and arrays using templates. This is a complex topic with many applications … too many for me to list, but I’ve shown some of the more common uses I can think of.

Destructuring docs and MDN docs

function f() {
  return [1, 2];
}
let [a, b] = f()
print("a="+a + " b=" + b)

const obj = {state: {id: 1, is_verified: false}}
const {id, is_verified: verified} = obj.state
print("id = " + id)
print("verified = " + verified)

for (const [key, value] of Object.entries({a: 1, b: 2, c: 3})) {
  print(key + " is " + value);
}
      

Shorthand functions aka Methods

Functions declared for objects can use the new shorthand style, which lacks the function keyword.

The two functions (fn1, fn2) are equivalent in the example below.

Method guide

const x = {
  type: "x",
  shorthand() {
    print("shorthand "+this.type)
  },
  long: function() {
    print("long "+this.type)
  }
}
x.shorthand()
x.long()

Promise.all

I’ve mostly skipped promises because async / await is preferred, but sometimes you need to synchronize multiple asynchronous calls and Promise.all is the easiest way to do it.

Promise.all documentation

const delay = (n) => {
  return new Promise((resolve) => {
    setTimeout(()=> resolve(n), n)
  })
}
async function main() {
  const delays = [100, 200, 300].map(n => delay(n))
  print("waiting…")
  const res = await Promise.all(delays)
  print("done. result is " + res)
}
main()
      

Template literals

This new syntax, also known as pattern strings, provides easy string interpolation and multi-line strings.

Template literal docs

let x = `multi
      line
string`
print(x)

x = `1+1=${1+1}`
print(x)

Proxy

A proxy allows you to intercept get / set calls on another object. This can be useful for tracking property changes, later updating the DOM, or creating innovative APIssuch as the www proxy below.

image

Proxy docs

let _nums = [1,2,3]
let nums = new Proxy(_nums, {
  set(target, key, value) {
    target[key] = value
    print("set called with " + key + "=" + value)
    print("update DOM")
    return true
  }
})
nums.push(4)
print("nums: " + nums)
print("_nums: " + _nums)

Module import / export

Modules allow you to create a namespace for your code and break down functionality into smaller files. In the example below, we have a module named greet.js that is included in index.html. Please note that module loading is always deferred, so it does not block HTML rendering. There are many ways to import / export functionality from js files, read more in export documentation

Import docs

function greet(msg) {
  console.log("greet:", msg)
}
export default greet

A file named “greet.js” in the “/ js” directory.

<script type="module">
  import greet from "/js/greet.js"
  greet("hi")
</script>

index.html

Read more

So, I did not talk about everything that has changed over the past decade, but only about what I find most useful. Check out these other topics.

References

Guides

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *