Modern Javascript: Everything You’ve Missed Out In The Last 10 Years
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 ()
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 let
if the value changes.
?? 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 await
to 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 ()}.
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 return
if 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.
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.
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.
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 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.
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.
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.
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
…
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.
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.
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.
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.
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.
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…
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