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
( , , )
Array functions
Check out all of these new native 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). Usage
const
implies that the value that the value should not change, but
let
gives this opportunity.
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
await
to make an asynchronous call look like a synchronous call, i.e. execution
await fetchUserName()
will not advance to the next line until fetchUserName () has completed. Please note that in order to use
await
, you must be executing 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 types 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.
A multi-line form requires a statement
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.
Arrow function docs
for ... of
Used to iterate over an iterator. Likewise
for...in
, except 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
Object.entries ({})
to get iteration instead .
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
Through the magic of object destructuring, 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}
although keep in mind 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 template strings, provides simple 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, updating the DOM later, or creating innovative APIs like the www proxy below. 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 the export documentation for details .
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