JavaScript from ES6 to ES12 in 2022

Ken Huang
15 min readOct 10, 2022

--

JavaScript from ES6 to ES12

Foreword

Javascript a one of the three most crucial programming at front-end positions. However, the author Brendan Eich just spent 10 days on it. It helps a lot in the website technically, but it also caused many issues to fix, that’s why there are from ES6(2015) to ES12(2021) born to fix better JavaScript.

ECMA Next Generation JavaScript

How many developers wrote code to solve kinds of problems for many evenings? we don’t know…

Examples of JavaScript defect

Although there is TypeScript to help avoid some defects of JavaScript, the root problem is still JavaScript. So that there are some new scripts to support software engineers by ES6 ~ ES12 for now.

ECMAScript

The ECMA specification is made up of parties, including browser vendors, who meet to push JavaScript proposals.

ES6 (ES2015)

  1. Class

JavaScript is a language that uses the prototype chain
In the early days, concepts similar to OO were made through the prototype chain. The writing is quite complicated. CLASS finally launched in ES6

class Animal {
constructor(name, color) {
this.name = name;
this.color = color;
}
// This is a property on the prototype chain
toString() {
console.log('name:' + this.name + ', color:' + this.color);

}
}

var animal = new Animal('myDog', 'yellow'); // instantiate
animal.toString(); // name: myDog, color: yellow

console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true

class Cat extends Animal {
constructor(action) {
// The subclass must call the super function in the constructor, otherwise an error will be reported when new comes out
// If the constructor was not written originally, the default constructor with super will be automatically generated
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}

var cat = new Cat('catch')
cat.toString();

console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true

2. Module

Each module has its own namespace to avoid conflicts, use import and export to import and export.

Basically treats a .js file as a module.

3. Arrow function

() => {…}, short for function. Most importantly, he can ensure that this always points to himself
No more writing var self = this, var that = this, etc!

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

const res = add(1, 2); // 3

// If the syntax is simple, `{}` and `return` can be omitted. It will look neater
const minus = (a, b) => a - b;
const res1 = minus(3, 1); // 2

4. Function parameter default value

If the function does not pass parameters, the default value is used. more condensed writing.

function example(height = 50, width = 40) { 
const newH = height * 10;
const newW = width * 10;
return newH + newW;
}

example(); // 900 (50*10 + 40*10)

5. Template literal

The composition of long strings, in the past, was concatenated through +.

Its readability is pretty bad. With template strings, it’s much easier to read.

const firstName = 'Ken';
const lastName = 'Huang';
// not use template literal
const name = 'Hello, My name is' + firstName + ', ' + lastName;
// use template literal
const nameWithLiteralString = `Hello, My name is ${firstName}, ${lastName}`;

6. Destructuring assignment

Allow JavaScript to easily get content from arrays and objects.

const arr = [1, 2, 3, 4, 5];
const [one, two, three] = arr;
console.log(one); // 1
console.log(two); // 2
console.log(three); // 3

// To skip certain values
const [first,,,,last] = arr;
console.log(first); // 1
console.log(last); // 5

// Objects can also be destructurized and assigned
const student = {
name: 'Ken Huang',
age: 38,
city: 'Taipei'
};
const {name, age, city} = student;
console.log(name); // "Ken Huang"
console.log(age); // "38"
console.log(city); // "Taipei"

7. Spread operator

That is…, Array can be expanded, if it is an Object, it will be expanded according to key-value.

const stuendts = ['Angel', 'Ryan']; 
const people = ['Sara', ...stuendts, 'Kelly', 'Eason'];
conslog.log(people); // ["Sara", "Angel", "Ryan", "Kelly", "Eason"]

8. Object property shorthand

The value can be omitted if the field names that make up the object are the same as the variables in the preceding paragraphs. looks more streamlined.

const name = 'Angel', age = 18, city = 'ChangHwa';

// Before ES6, we must write like this
const customer = {
name: name,
age: age,
city: city
} // // {name: 'Angel', age: 18, city: 'ChangHwa'}

// After ES6, we can do it
const newCustomer = {
name,
age,
city
} // {name: '小明Angel, age: 18, city: 'ChangHwa'}

9. Promise

The promise is a solution to asynchronous (non-synchronous) writing, which is more elegant than the original callback writing.

In the early days, it was a suite of the open-source community, and later it was incorporated into the language standard.

Early callback hell…

callback hell of JavaScript

After using promises, the callback hell is flattened

const waitSecond = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
waitSecond.then( () => {
console.log('hello World after 1 second.');
// output this line after 1 second
return waitSecond;
}).then( () => {
console.log('Hell World after 2 sceond.');
// output this line after 2second
})

And ES8 (ES2017) releases a more perfect async, await, which directly makes asynchronous writing like synchronization.

The disadvantage is that when thoughts fall on complex business logic, await is sometimes missed, and errors are found at runtime.

10. let, const to replace var

let: general variable, can be overridden

const: Once declared, its contents cannot be modified. Because arrays and objects are indicators, their contents can be increased or decreased. but not to change its indicators

In the early days, the var scope of js was global.

That is, the variable is declared after you use it. When it is executed, it will be automatically mentioned to the top level, and it will be assigned later.
more prone to contamination.

console.log(a); // undefined
var a = 10;

using let or const

console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;

ES7 (ES2016)

  1. Array.prototype.includes()

Used to determine whether the array contains the specified value, if so, return true; otherwise, return false.

Like the previous usage of indexOf, it can be thought of as returning a boolean, which is more semantically clear.

const arr = [1, 2, 3, 4, 5];
// Check if there is the number 3 in the array
arr.include(3); // true

if (arr.include(3)) { ... }
// ... Equivalent to the previous writing of indexOf
arr.indexOf(3); // 2 (return its array position)
// If you want to write it in the if, you must add `> -1`, which is not as clear as the include in ES7 in terms of semantics
if (arr.indexOf(3) > -1) { ... }

2. Exponentiation Operator

console.log(2**10); // 1024
// equal to
console.log(Math.pow(2, 10)); // 1024

ES8 (ES2017)

  1. async, await

An async function is a function declared with the async keyword, and the await the keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

async test() {
try {
const result = await otherAsyncFunction();
console.log(result); // output result
} catch(e) {
console.log(e); // Can catch errors if otherAsyncFunction() throws an error
}
}

2. Object.values()

Returns all the values of the Object’s own properties, excluding inherited values.

const exampleObj = {a: 1, b: 2, c: 3, d:4};
console.log(Object.value(exampleObj)); // [1, 2, 3, 4];

// To do the same thing before, use the following notation. much verbose
const values = Object.keys(exampleObj).map(key => exampleObj[key]);

3. Object.entries()

Returns the enumerable key, the value of the incoming object itself.

const exampleObj = {a: 1, b: 2, c: 3, d:4};
console.log(Object.entries(exampleObj)); // [["a", 1], ["b", 2], ["c", 3], ["d", 4]];

// Usually used with for
for (const [key, value] of Object.entries(exampleObj)) {
console.log(`key: ${key}, value: ${value}`);
}
// key: a, value: 1
// key: b, value: 2
// key: c, value: 3
// key: d, value: 4

4. String padStart() & padEnd()

You can add other content to the beginning or end of the string and fill it to the specified length.

In the past, these functions are usually introduced with universal auxiliary kits (such as lodash) and had them together.

Native syntax now provides direct:

String.padStart(fillingLength, FillingContent);
// If the content to be filled is too much and exceeds the "filling length", it will be filled from the far left to the upper limit of the length, and the excess part will be truncated

The most commonly used situation should be the amount, fill in the specified length, and add 0 if it is insufficient.

// padStart
'100'.padStart(5, 0); // 00100
// If the content to be padded exceeds the "padding length". Then fill in from the left to the upper limit of the length
'100'.padStart(5, '987'); // 98100

// padEnd
'100'.padEnd(5, 9); // 10099
// If the content to be padded exceeds the "padding length". Then fill in from the right to the upper limit of the length
'100'.padStart(5, '987'); // 10098

5. Trailing commas

Allow commas at the end of function parameter lists

[
"foo",
+ "baz",
"bar",
- "baz",
]

6. Object.getOwnPropertyDescriptors()

Get your own Descriptor. General development business requirements are usually not used.

const exampleObj = {a: 1, b: 2, c: 3, d:4};

Object.getOwnPropertyDescriptors(exampleObj);
// {a: {…}, b: {…}, c: {…}, d: {…}}
// a: {value: 1, writable: true, enumerable: true, configurable: true}
// b: {value: 2, writable: true, enumerable: true, configurable: true}
// c: {value: 3, writable: true, enumerable: true, configurable: true}
// d: {value: 4, writable: true, enumerable: true, configurable: true}
// __proto__: Object

7. SharedArrayBuffer

SharedArrayBuffer is a fixed-length raw binary data buffer, similar to ArrayBuffer.

Can be used to create data on shared memory. Unlike ArrayBuffer, SharedArrayBuffer cannot be detached.

/**
* @param length size,unit byte
* @returns {SharedArrayBuffer} the default value is 0。
*/
const sab = new SharedArrayBuffer(length);

8. Atomics object

Atomics object, which provides a set of static methods to perform atomic operations on SharedArrayBuffer.

All properties and functions of Atomics are static, just like Math. can’t come out new.

If a multi-thread reads and writes data at the same location at the same time, the atomic operation ensures that the data being operated is as expected: that is, the next one will be performed after the previous sub-operation has ended. Operation is not interrupted

It can be said that it is a function that is reinforced in response to the development of a multi-thread Server in Node.Js, and the opportunity to use it in front-end development is quite low.
chrome already provides support

ES9 (ES2018)

  1. await in loop

In an async function, it is sometimes necessary to use an asynchronous (non-synchronous) function in a synchronous for-loop.

async function process(array) {
for (const i of array) {
await doSomething(i);
}
}

async function process(array) {
array.forEach(async i => {
await doSomething(i);
});
}

The above code will not output the desired result as expected
The for-loop itself is still synchronous and will execute the entire for-loop before the asynchronous functions in the loop are completed, and then execute the asynchronous functions in it one by one.

ES9 adds asynchronous iterators, allowing await to be used with for-loops to perform asynchronous operations step by step.

async function process(array) {
for await (const i of array) {
doSomething(i);
}
}

2. promise.finally()

The part of Promise that will be executed later whether it succeeds (.then()) or fails (.catch()).

function process() {
process1()
.then(process2)
.then(process3)
.catch(err => {
console.log(err);
})
.finally(() => {
console.log(`it must execut no matter success or fail`);
});
}

3. Rest, Spread

In ES2015, the Rest indefinite length parameters… can be converted into an array and passed in.

function restParams(p1, p2, ...p3) {
console.log(p1); // 1
console.log(p2); // 2
console.log(p3); // [3, 4, 5]
}
restParams(1, 2, 3, 4, 5);

And the spread is the opposite of the rest, converting the array into a separate parameter.

For example, Math.max() returns the maximum value in the incoming number.

const values = [19, 90, -2, 6, 25];
console.log( Math.max(...values) ); // 90

It also provides the function of destructuring assignments for Objects.

const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...r } = myObject;
// a = 1
// r = { b: 2, c: 3 }

// Can also be used in function input parameters
function restObjectInParam({ a, ...r }) {
console.log(a); // 1
console.log(r); // {b: 2, c: 3}
}

restObjectInParam({
a: 1,
b: 2,
c: 3
});

4. RegExp groups

RegExp can return matching packets

const regExpDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const match = regExpDate.exec('2020-06-25');
const year = match[1]; // 2020
const month = match[2]; // 06
const day = match[3]; // 25

5. Regexp lookahead Negative

TBD

6. Regexp dotAll

.Represents matching any symbol except entering. After adding thes flag it is allowed to match enter.

/hello.world/.test('hello\nworld');  // false
/hello.world/s.test('hello\nworld'); // true

ES10 (ES2019)

  1. Better friendly JSON.stringify

Originally JSON.stringify would return a malformed Unicode string if the input was in Unicode but out of range.

Now a stage 3 proposal to make it valid Unicode and render in UTF-8

2. Array.prototype.flat() & Array.prototype.flatMap()

flattens the array

const arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat(); // [1, 2, 3, 4, [5, 6]]
// Pass in a number in flat, representing the flattening depth
arr2.flat(2); // [1, 2, 3, 4, 5, 6]

flatMap(), equivalent to reduce with concat, can flatten a dimension

let arr = ["早安", "", "今天天氣不錯"]

arr.map(s => s.split(""))
// [["早", "安"], [""], ["今", "天", "天", "氣", "不", "錯"]]

arr.flatMap(s => s.split(''));
// ["早", "安", "", "今", "天", "天", "氣", "不", "錯"]

3. String.prototype.trimStart() & String.prototype.trimEnd()

The trimStart() method removes whitespace from the beginning of a string. trimLeft() is an alias of this method.

const greeting = ‘ Hello world! ‘;console.log(greeting);
// expected output: “ Hello world! “;
console.log(greeting.trimStart());
// expected output: “Hello world! “;

The trimEnd() method removes whitespace from the end of a string. trimRight() is an alias of this method.

const greeting = '   Hello world!   ';console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimEnd());
// expected output: " Hello world!";

4. Object.fromEntries()

The Object.fromEntries() method transforms a list of key-value pairs into an object.

const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
const obj = Object.fromEntries(entries);console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }

5. String.prototype.matchAll

The matchAll() method returns an iterator of all results matching a string against a regular expression, including capturing groups.

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

6. fixed catch bind

Before using catch, whether it is useful or not, be sure to pass in an eparameter to represent the error received.

If it is not used now, you can omit it.

try {...} catch(e) {...}

// If e is not used, it can be omitted
try {...} catch {...}

7. BigInt (new number type)

A BigInt value, also sometimes just called a BigInt, is a bigint primitive, created by appending n to the end of an integer literal, or by calling the BigInt() function (without the new operator) and giving it an integer value or string value.

ES5: String, Number, Boolean, Null, Undefined
ES6 Added: Symbol, 6 types
ES10 added: BigInt, reaching 7 types

const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n

const hugeString = BigInt("9007199254740991");
// ↪ 9007199254740991n

const hugeHex = BigInt("0x1fffffffffffff");
// ↪ 9007199254740991n

const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111");
// ↪ 9007199254740991n

ES11 (ES2020)

  1. Promise.allSettled

The Promise.allSettled() method returns a promise that fulfilled after all of the given promises have either been fulfilled or rejected, with an array of objects that each describes the outcome of each promise.

It is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you’d always like to know the result of each promise.

In comparison, the Promise returned by Promise.all() maybe more appropriate if the tasks are dependent on each other / if you'd like to immediately reject any of them reject.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"

2. Optional chaining ?

In development, it is easy to encounter checking whether the data exists and writing if judgment first.

const isUserExist = user && user.info;
if (isUserExist) {
username = user.info.name;
}

If the returned data is null or there is no .intounder the user object, Uncaught TypeError: Cannot read property... will be thrown.
cause the program cannot continue to execute

With ?, the syntax is much simpler

const username = user?.info?.name;

If it exists, get the value of the name, if it does not exist, assign undefined

Use with ||, just one line!

const username = user?.name || 'guest';

3. Nullish coalescing operator ??

In js, when encountering 0, null, and undefubed, it will be automatically converted to false

But sometimes 0 is actually a normal value, and can only be fault-tolerant to undefinedand null

/**
* user = {
* level: 0
* }
*/
const level = user.level || 'no level'; // output as no level instead of expected 0
// to fix it, it must use if simple processing
const level = user.level !== undefined && user.level !== null ? user.level : 'no level';

But with ??, you can keep it concise

const username = user.level ?? 'no level'; 
// output 0. if level is not available, it becomes 'no level'.

4. Dynamic-import

Literally, it should be easy to understand. It is to load the relevant logic when necessary.

el.onclick = () => {
import(`/js/current-logic.js`)
.then((module) => {
module.doSomthing();
})
.catch((err) => {
handleError(err);
})
}

5. GlobalThis

The global globalThis the property contains the global this value, which is akin to the global object.

In the past the practice was

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis
const getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};

var globals = getGlobal();

for now, we can do like this

function canMakeHTTPRequest() {
return typeof globalThis.XMLHttpRequest === 'function';
}
console.log(canMakeHTTPRequest());
// expected output (in a browser): true

ES12 (ES2021)

  1. Promise.any

Promise.any() takes an iterable of Promise objects. It returns a single promise that fulfills as soon as any of the promises in the iterable fulfills, with the value of the fulfilled promise. If no promises in the iterable fulfill (if all of the given promises are rejected), then the returned promise is rejected with an AggregateError, a new subclass of Error that groups together individual errors.

const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('p1 resolved value')
}, 1000)
})
const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve('p2 resolved value')
}, 500)
})
const p3 = new Promise((resolve) => {
setTimeout(() => {
resolve('p3 resolved value')
}, 1800)
})
Promise.any([p1, p2, p3]).then(value=>{
console.log(value)
}) // p2 resolved value

2. Logical Assignment Operator

During development, we can use the logical operator ||, &&and the ??(Nullish coalescing operator) proposed in ES2020 to solve some problems.

And ES2021 will propose ||= , &&= , ??=, the concept is similar to +=:

let b = 2
b += 1
// equal to b = b + 1
let a = null
a ||= 'some random text' // a become to'some random text'
// equal a = a || 'some random text'
let c = 'some random texts'
c &&= null // c become to null
// equal to c = c && null
let d = null
d ??= false // d become to false
// equal to d = d ?? false

3. WeakRef

A WeakRef object contains a weak reference to an object, which is called its target or referent. A weak reference to an object is a reference that does not prevent the object from being reclaimed by the garbage collector. In contrast, a normal (or strong) reference keeps an object in memory. When an object no longer has any strong references to it, the JavaScript engine's garbage collector may destroy the object and reclaim its memory. If that happens, you can't get the object from a weak reference anymore.

This example starts a counter shown in a DOM element, stopping when the element doesn’t exist anymore:

class Counter {
constructor(element) {
// Remember a weak reference to the DOM element
this.ref = new WeakRef(element);
this.start();
}

start() {
if (this.timer) {
return;
}

this.count = 0;

const tick = () => {
// Get the element from the weak reference, if it still exists
const element = this.ref.deref();
if (element) {
element.textContent = ++this.count;
} else {
// The element doesn't exist anymore
console.log("The element is gone.");
this.stop();
this.ref = null;
}
};

tick();
this.timer = setInterval(tick, 1000);
}

stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0;
}
}
}

const counter = new Counter(document.getElementById("counter"));
setTimeout(() => {
document.getElementById("counter").remove();
}, 5000);

It’s my practice and note, If this was useful, please click the clap 👏 button down below a few times to show your support! Any mistakes feel free to point them out any and let me know, thanks!

--

--

Ken Huang

Frontend Engineer / Product Designer, ex-Shopee, ex-TrendMicro|https://kenhuang.tw