Chặng đường phát triển của Javascript từ ES6 đến ES12
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
ECMAScript
Là một bản đặc tả kỹ thuật (specification) cung cấp các quy tắc, chi tiết, hướng dẫn mà một ngôn ngữ kịch bản (scripting language) phải tuân theo cho một vài mục đích chung.
Nói thì hơi khó hiểu, nhưng bạn có thể hiểu nôm na là: một nhóm các ông lớn trong lĩnh vực phần mềm (bao gồm nhưng không giới hạn các nhà cung cấp trình duyệt web) ngồi lại với nhau, sau đó đưa ra một concept rồi mọi người làm theo.
Cụ thể thì bản đặc tả này giúp chúng ta có một cách viết chung cho Javascript, quên đi cái thời mỗi trình duyệt chạy Javascript mỗi kiểu.
ES6 (hay ES2015)
Bản ES6 này đem đến cho chúng ta rất nhiều tính năng hữu ích, một trong số quan trọng nhất có thể kể đến như: classes, arrow functions, template string, destructuring, promises...
Đây cũng là phiên bản mang đến rất nhiều sự thay đổi, ảnh hưởng tới sự phát triển rực rỡ của Javascript tới sau này.
1. Classes
Javascript vốn dĩ là một ngôn ngữ xây dựng trên concept Prototype. Tất nhiên, nó support OOP tạm bợ kèm theo cách viết phức tạp.
Bản ES6 này cuối cùng đã đưa ra định nghĩa Class (khá quen thuộc trong các ngôn ngữ lập trình khác như PHP, .NET, Java...).
class Animal {
constructor(name, color) {
this.name = name;
this.color = color;
}
// Đây là một property trong prototypy
toString() {
console.log(`name ${this.name}, color ${this.color}`);
}
}
var animal = new Animal('myDog', 'yellow'); // khởi tạo class
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) {
// Class này kế thừa Animal, do đó cần gọi super function trong constructor.
super('cat','white');
this.action = action;
}
toString() {
console.log(`${super.toString()}, action ${this.action}`);
}
}
var cat = new Cat('catch')
cat.toString(); // name cat, color white, action catch
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
2. Arrow functions
Là một cách viết function nhanh chóng mà không phải khai báo từ khóa function
.
Khác với cách khai báo function
thông thường, arrow function chia sẻ this
context (cũng như arguments
) giống phần code bên ngoài của chúng.
const add = (a, b) => {
return a + b
}
// Nếu nội dung trong function body đơn giản, bạn có thể rút gọn luôn 2 dấu ngoặc {}
const add = (a, b) => a + b
// Sử dụng chung this context với object bên ngoài.
const someone = {
_name: "John Doe",
_friends: ["Jane Doe"],
printFriends() {
this._friends.forEach(f =>
console.log(`${this._name} knows ${f}`))
}
}
// Sử dụng chung arguments với function bên ngoài
function square() {
let doSquare = () => {
let numbers = []
for (let number of arguments) {
numbers.push(number * number)
}
return numbers
}
return doSquare()
}
Quên đi các kiểu viết như vầy đi nha :D
const _self = this;
const that = this;
3. Template string
Template string cung cấp giải pháp tốt hơn thay thế việc cộng từng chuỗi nhỏ theo cách truyền thống.
Người ta nói nó giúp hạn chế các kiểu tấn công như injection attacks, nhưng cá nhân mình thì thấy nó giúp code dễ đọc hơn thôi :D.
// Template string cơ bản, không có tham biến
`This is a pretty little template string.`
// Chuỗi với nhiều dòng
`In ES5 this is
not legal.`
// Thêm vào các tham biến
const name = "Bob", time = "today"
`Hello ${name}, how are you ${time}?`
Ngoài ra, có một tính năng nữa khá hay với template string, đó là Tagged templates.
function tagged(strings, person, age) {
const str0 = strings[0]; // "That "
const str1 = strings[1]; // " is a "
const str2 = strings[2]; // "."
const ageStr = age > 99 ? "centenarian" : "youngster"
return `${str0}${person}${str1}${ageStr}${str2}`
}
const output = tagged`That ${'Mike'} is a ${age}.`
// That Mike is a youngster.
React có một package styled-components khá nổi tiếng cũng sử dụng tagged templates.
4. Destructuring
Destructuring giúp chúng ta xử lý nội dung của Array
và Object
dễ dàng hơn.
// Gán biến theo thứ tự thành phần trong Array
const [a, b, c] = [1, 2, 3]
a === 1 // true
b === 2 // true
c === 3 // true
// Bạn cũng có thể bỏ qua vài tham số nếu không cần thiết
const [a, , c] = [1, 2, 3]
c === 3 // true
// Thực hiện điều tương tự với Object
const student = {
name: 'John Doe',
age: 31,
country: 'Vietnam'
}
const { name, age, country } = student
name === 'John Doe' // true
age === 31 // true
country === 'Vietnam' // true
5. Default value cho tham số
Nếu bạn không nhập tham số, nó sẽ tự động lấy giá trị mặc định.
function f(x, y = 12) {
return x + y
}
f(3) === 15 // true
6. Rest syntax
function restSample(x, ...rest) {
// rest là một Array
return x * rest.length
}
restSample(3, "hello", "John Doe", "Vietnam") === 9 // true
const student = {
name: 'John Doe',
age: 31,
country: 'Vietnam'
}
const { name, ...rest } = student
name === 'John Doe' // true
rest.age === 31 // true
rest.country === 'Vietnam' // true
rest.name === undefined // true
7. Spread syntax
# Cú pháp spead
function spreadSample(x, y, z) {
return x + y + z;
}
// Do function spreadSample chỉ nhận 3 tham số
// Khi bạn pass 6 tham số nó cũng chỉ thao tác trên 3 tham số đầu 1,2,3
spreadSample(...[1, 2, 3, 4, 5, 6]) === 6 // true
# Sử dụng với Array
const students = ['Brian', 'Ronnie']
const people = ['Ken', ...students, 'Tom', 'Justin']
// ['Ken', 'Brian', 'Ronnie', 'Tom', 'Justin']
# Sử dụng với Object
const student = {
name: 'John Doe',
age: 31,
country: 'Vietnam'
}
const studentB = {
...student,
name: 'Jane Doe'
}
studentB.age === 31 // true
studentB.country === 'Vietnam' // true
studentB.name === 'Jane Doe' // true
8. Khai báo object nâng cao
Hỗ trợ các kiểu viết nhanh (shorthand) khi khai báo property, method cũng như thực hiện gọi hàm super
.
const ageField = 'age'
const address = '290 An Duong Vuong, HCMC, Vietnam'
const student = {
// Khai báo field mặc định, giống ES5
name: 'John Doe',
// Khai báo field dynamic
[ageField]: 18,
// Viết tắt, thay vì phải viết `address: address`
address,
// Khai báo nhanh phương thức trong object
// Thay vì phải viết `toString: () {...}`
toString() {
// Super call
return 'Student ' + super.toString()
}
}
9. Sử dụng let/const thay thế cho var
let
: biến chung trong một scope cụ thể, có thể bị ghi đè.
const
: không thể ghi đè. Tuy nhiên, đối với các đối tượng indicators sau:
Array
: không thể ghi đè, nhưng có thể thêm vào hoặc xóa bớt các elements của nó.Object
: cũng không thể ghi đè, nhưng có thể thay đổi nội dung của các properties.
Cả let
/const
đều chỉ hoạt động trong một scope cụ thể. Tham khảo ví dụ sau đây:
function scope() {
{
let x
{
// Đoạn code này ok vì lúc này chúng ta đã ở trong một block scoped mới
const x = 'John Doe'
// Lỗi rồi nha, x đã được khai báo trong cùng block với từ khóa `const` ở trên
x = 'Jane Doe'
{
// Đoạn code này ok vì lúc này chúng ta đã ở trong một block scoped mới
let x = 'Justin'
}
}
// x đã được khai báo trước đo với let, nên bạn có thể assign nó tùy ý
x = 'Ronnie'
// Lỗi rồi nha, x đã được khai báo trong cùng block với từ khóa `let` ở trên
let x = "inner"
}
}
10. Iterators và for ... of ...
Câu lệnh for ... of ... thực hiện vòng lặp trên một danh sách các giá trị theo thứ tự từ một interable object.
Mặc định, Javascript tích hợp sẵn các iterable objects như Array
, String
, TypedArray
, Map
, Set
, NodeList
, các DOM collections, đối tượng arguments
.
Ngoài ra, chúng ta cũng có thể tự define các iterable objects giống như ví dụ dưới đây.
interface IteratorResult {
done: boolean
value: any
}
interface Iterator {
next(): IteratorResult
}
interface Iterable {
[Symbol.iterator](): Iterator
}
let fibonacci: Iterable = {
[Symbol.iterator]() {
let pre = 0,
cur = 1
return {
next() {
[pre, cur] = [cur, pre + cur]
return { done: false, value: cur }
},
}
},
}
for (const n of fibonacci) {
// Break loop khi value lớn hơn 1000000, không thì toang
if (n > 1_000_000)
break;
console.log(n);
}
11. Generators
Generators đơn giản hóa quá trình tạo ra các iterators thông qua từ khóa function*
và yield
. Một function được khai báo với function*
sẽ trả về một Generator instance.
Generator là một kiểu con (subtype) của iterators, bao gồm next
và throw
.
Từ khóa yield
là một expression trả về giá trị (giống như return
vậy đó). Bạn cũng có thể throw
exception nếu muốn.
const fibonacci: { [Symbol.iterator]: () => Generator<number> } = {
[Symbol.iterator]: function* () {
let pre = 0, cur = 1
for (;;) {
[pre, cur] = [cur, pre + cur]
yield cur
}
},
}
for (const n of fibonacci) {
// Break loop khi value lớn hơn 1000000
if (n > 1_000_000) break
console.log(n)
}
12. Modules
Bạn có thể viết code ở nhiều file khác nhau để dễ quản lý, mỗi file sẽ được xem như một module. Bạn chỉ cần export
chúng, sau đó import
vào nơi bạn cần sử dụng.
// lib/math.js
export function sum(x, y) {
return x + y
}
export const pi = 3.141593
// app.js
import * as math from "lib/math"
console.log("2π = " + math.sum(math.pi, math.pi))
// otherApp.js
import {sum, pi} from "lib/math"
console.log("2π = " + sum(pi, pi))
Ngoài ra, ES6 còn cung cấp thêm 2 biểu thức đặc biệt là export default
và export *
.
// lib/mathplusplus.js
export * from "lib/math"
export var e = 2.71828182846
export default function(x) {
return Math.exp(x)
}
// app.js
import exp, {pi, e} from "lib/mathplusplus"
console.log("e^π = " + exp(pi))
13. Map - WeakMap - Set - WeakSet
Map
- WeakMap
- Set
- WeakSet
là các kiểu dữ liệu mới được ra đời để xử lý Collection - hay dữ liệu có cấu trúc, giúp giải quyết những thiếu sót mà các kiểu dữ liệu thông thường (như Object, Array) gặp phải, đồng thời mang lại tốc độ xử lý tốt hơn.
Set
Set
là một collection các phần tử khác nhau và không có key. Dữ liệu trong Set
luôn luôn là duy nhất (unique).
// Khởi tạo Set
const blankSet = new Set()
// Khởi tạo Set có khai báo giá trị
const set: Set<string | number> = new Set(['a', 'b', 'c', 'd', 1, 2, 3])
// Thêm phần tử
const s: Set<string> = new Set()
s.add('hello').add('goodbye').add('hello')
// Đếm số phần tử
s.size === 2 // true
// Kiểm tra một phần tử có tồn tại hay không
s.has('hello') // true
// Xóa phần tử
const char = new Set(['a', 'b', 'c'])
char.delete('c') // char = Set(2) {"a", "b"}
// Loop qua các phần tử
// Do Set là kiểu dữ liệu iterators, do đó chúng ta cần dùng for...of
const chars = new Set(['a', 'b', 'c'])
for (const char of chars){
console.log(char)
}
// Convert một Set sang Array
const char = new Set(['a', 'b', 'c'])
const arrChar = [...char] // ['a', 'b', 'c']
WeakSet
Tương tự như Set
, tuy nhiên có một vài khác biệt lớn:
- dữ liệu truyền vào
WeakSet
phải làobject
,class
hoặcfunction
. - không có thuộc tính
size
, nên chỉ có loop từ từ. - nó cũng không có các methods như
clear
,keys
,values
,entries
,forEach
nhưSet
. - do đó, nó cũng không thuộc kiểu iterators.
// Khởi tạo WeakSet
const ws = new WeakSet()
const value = { data: 42 }
ws.add(value)
Một điểm quan trọng nữa là bạn cần tạo biến cho từng element trước khi add vào WeakSet
, nếu không Javascript sẽ coi như đó là dữ liệu rác và thực hiện garbage collected.
// Khởi tạo WeakSet
const ws = new WeakSet()
const value = { data: 42 }
ws.add(value) // [{ data: 42 }]
// Do element truyền vào không có references ở đâu cả
// Nên hệ thống tự động thực hiện garbage collected.
ws.add({ data: 43 }) // [{ data: 42 }]
Thực ra mình cũng chưa có dịp xài thằng này bao giờ.
Map
Thằng này thì nó cũng tương tự như Set
, nhưng Map
sẽ có cấu trúc dạng key => value
, trong khi Set
chỉ có value
.
key
của Map
có thể là bất cứ thứ gì (string
, number
, symbol
, NaN
, class
, function
,...)
Đây cũng là thứ mà NestJS lưu trữ các Provider
để thực hiện dependency injection.
const m = new Map()
m.set('hello', 42)
m.get('hello') === 42 // true
// Key có thể là bất cứ cái gì bạn muốn
const set = new Set()
m.set(set, 34)
m.get(set) === 34 // true
WeakMap
Thằng này cũng tương tự WeakSet
, tuy nhiên WeakSet
thì dựa trên value
, còn WeakMap
dựa trên key
nha mọi người.
Thằng này mình cũng chưa xài bao giờ luôn :D
14. Proxy
Đây là một tính năng cực kỳ cực kỳ hay ho mà ES6 cung cấp. Nó giúp chúng ta thực hiện những thứ tương tự như Magic Methods trong PHP.
Bạn có thể hiểu nó wrap lại một variable, khi bạn thao tác một hành động nào đó, nó sẽ chặn lại và thực hiện một vài xử lý cụ thể mà bạn muốn.
const proxy = new Proxy(target, handler)
target
: đối tượng bạn cần wraphandler
: nó được gọi làproxy configuration
- hoặctrap
.
// Thực hiện proxy một object thường
const target = {}
const handler = {
get: function (receiver, name) {
return `Hello, ${name}!`
}
}
const p = new Proxy(target, handler)
p.world === "Hello, world!" // true
// Thực hiện proxy một function
const target = function () {
return 'I am the target'
}
const handler = {
apply: function (receiver, ...args) {
return 'I am the proxy'
},
}
const p = new Proxy(target, handler)
p() === 'I am the proxy' // true
Sau đây là danh sách các trap
mà proxy hiện tại đang hỗ trợ:
const handler = {
// target.prop
get: ...,
// target.prop = value
set: ...,
// 'prop' in target
has: ...,
// delete target.prop
deleteProperty: ...,
// target(...args)
apply: ...,
// new target(...args)
construct: ...,
// Object.getOwnPropertyDescriptor(target, 'prop')
getOwnPropertyDescriptor: ...,
// Object.defineProperty(target, 'prop', descriptor)
defineProperty: ...,
// Object.getPrototypeOf(target), Reflect.getPrototypeOf(target),
// target.__proto__, object.isPrototypeOf(target), object instanceof target
getPrototypeOf: ...,
// Object.setPrototypeOf(target), Reflect.setPrototypeOf(target)
setPrototypeOf: ...,
// for (let i in target) {}
enumerate: ...,
// Object.keys(target)
ownKeys: ...,
// Object.preventExtensions(target)
preventExtensions: ...,
// Object.isExtensible(target)
isExtensible :...
}
15. Symbol
Symbol là một kiểu dữ liệu nguyên thủy (primitive data types). Khi bạn tạo một symbol, giá trị của nó được giữ kín và chỉ sử dụng nội bộ.
Mỗi một symbol đều là duy nhất, có nghĩa là chúng không bao giờ có giá trị giống nhau, mặc dù nhìn trông giống nhau.
Ơ nghe ngáo nhỉ :D
const ABC = Symbol('ABC')
const ABC2 = Symbol('ABC')
ABC === ABC2 // false
Bạn cũng có thể khởi tạo symbol
bằng expression Symbol.for
. Lúc này chúng sẽ hoạt động như một singleton - nếu đã tồn tại thì trả về symbol đã tạo, nếu chưa có thì tạo mới.
const ABC3 = Symbol.for('ABC')
const ABC4 = Symbol.for('ABC')
ABC3 === ABC4 // true
Lưu ý: sử dụng Symbol.for
khi bạn muốn tạo một symbol
ở chỗ này nhưng lại sử dụng ở chỗ khác, tuy nhiên nó sẽ khiến symbol
mất đi tính duy nhất. Cho nên, tốt nhất bạn nên export
ra rồi sử dụng thay vì khai báo kiểu singleton này.
16. Math - Number - String - Object APIs
Có nhiều thư viện mới được thêm vào bản ES6 này.
Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN('NaN') // false
Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2
'abcde'.includes('cd') // true
'abc'.repeat(3) // "abcabcabc"
Array.from(document.querySelectorAll('*'))
Array.of(1, 2, 3)
[(0, 0, 0)].fill(7, 1) // [0,7,7]
[(1, 2, 3)].findIndex((x) => x == 2) // 1
[('a', 'b', 'c')].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
[('a', 'b', 'c')].keys() // iterator 0, 1, 2
[('a', 'b', 'c')].values() // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0, 0) })
17. Promises
Promise
là một giải pháp xử lý bất đồng bộ, với cách viết trang nhã, dễ đọc hơn so với callback
truyền thống.
Trước đó, Promise
đã được implement trong một vài thư viện của JS như Q, When, Bluebird... Từ bản ES6, nó đã được hỗ trợ trong JS core.
Một Promise
đại diện cho một value có thể chưa tồn tại ở thời điểm hiện tại, nhưng sẽ được xử lý và có value cụ thể vào một thời gian nào đó trong tương lai.
function timeout(duration = 0) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration)
})
}
const p = timeout(1000)
.then(() => {
return timeout(2000)
})
.then(() => {
throw new Error('hmm')
})
.catch((err) => {
return Promise.all([timeout(100), timeout(200)])
})
18. Reflect API
Ở ES5, nó tồn tại dưới dạng static method của Object
. Lên ES6 thì nó được tách ra thành Reflect
class.
Reflect không có constructor
, do đó bạn không thể khởi tạo một new instance. Mọi phương thức của nó đều là static
.
Các phương thức của Reflect
tương ứng với các traps
của proxy và kết quả trả về của nó cũng chính là kết quả trap
cần trả về.
const O = { a: 1 }
Object.defineProperty(O, 'b', { value: 2 })
O[Symbol('c')] = 3
Reflect.ownKeys(O) // ['a', 'b', Symbol(c)]
function C(a, b) {
this.c = a + b
}
const instance = Reflect.construct(C, [20, 22])
instance.c // 42
ES7 (hay ES2016)
TC39 giới thiệu rất nhiều features, nhưng release có vài cái thôi :D
1. Array.includes
Kiểm tra một phần tử có tồn tại hay không.
interface Array<T> {
includes(searchElement: T, fromIndex?: number): boolean
}
[1,2,3].includes(1) // true
2. Toán tử lũy thừa
Bạn có thể sử dụng toán tử **
thay cho cách viết thông qua library Math.pow
.
console.log(2**10) // 1024
// Tương tự với
console.log(Math.pow(2, 10))
ES8 (hay ES2017)
Release nhiều hơn bản ES7 một xíu :D
1. async - await
Promise trong ES6 cũng tuyệt vời đấy, nhưng lắm lúc nó lại đẻ ra cái promise hell như thế này
connectToDatabase().then((database) =>
getUser(database).then((user) =>
getUserSettings(database)
.then((settings) => enableAccess(user, settings))
)
)
Nhức nách quá phải không :D
Rất may async - await đã được sinh ra để giải quyết vấn đề này
const database = await connectToDatabase()
const user = await getUser(database)
const settings = await getUserSettings(database)
await enableAccess(user, settings)
Lúc này code trông clean hẳn rồi phải không :D
2. Object.values()
Trả về toàn bộ values của một object dưới dạng array.
const countries: Record<string, string> = {
BR: 'Brazil',
DE: 'Germany',
RO: 'Romania',
US: 'United States of America'
}
const values: string[] = Object.values(countries) // ['Brazil', 'Germany', 'Romania', 'United States of America']
3. Object.entries()
Trả về một array các pairs (mỗi pair là một array có 2 phần tử, phần tử đầu tiên là key
, phần tử thứ hai là value
).
const countries: Record<string, string> = {
BR: 'Brazil',
DE: 'Germany',
RO: 'Romania',
}
const entries: Array<[string, string]> = Object.entries(countries)
// [['BR', 'Brazil'], ['DE', 'Germany'], ['RO', 'Romania']]
4. Get tất cả descriptors của Object
Object.getOwnPropertyDescriptors()
trả về tất cả descriptor của một object (ngoại trừ các thuộc tính được extends từ object / class cha).
const obj = {
name: 'Pablo',
get foo() {
return 42
},
}
Object.getOwnPropertyDescriptors(obj)
//
// {
// "name": {
// "value": "Pablo",
// "writable":true,
// "enumerable":true,
// "configurable":true
// },
// "foo":{
// "enumerable":true,
// "configurable":true,
// "get": function foo()
// "set": undefined
// }
// }
5. String padding (padStart, padEnd)
interface String {
padStart(maxLength: number, fillString?: string): string
padEnd(maxLength: number, fillString?: string): string
}
// Trả về một string mới có độ dài bằng 10
// Có thêm 6 space phía trước, do độ dài ban đầu là 4 ký tự
'0.10'.padStart(10) // ' 0.10'
'0.10'.padStart(12) // ' 0.10'
'23.10'.padStart(12) // ' 23.10'
'12,330.10'.padStart(12) // ' 12,330.10'
'hi'.padStart(1) // 'hi'
'hi'.padStart(5) // ' hi'
'hi'.padStart(5, 'abcd') // 'abchi'
'hi'.padStart(10, 'abcd') // 'abcdabcdhi'
'loading'.padEnd(10, '.') // 'loading...'
6. Trailing commas
Cho phép tồn tại dấu phẩy ở sau tham số cuối cùng của một khi khai báo hoặc gọi một function
.
getDescription(name, age,) { ... }
ES8 có một vài thay đổi tuy nhỏ nhưng tác động rất lớn, nhưng đối với mình quan trọng nhất vẫn là các toán tử bất đồng bộ async
- await
.
ES9 (hay ES2018)
Thay đổi bự nhất chắc là cho phép thực hiện await
trong vòng loop.
1. await khi loop
Đôi khi chúng ta có nhu cầu thực hiện gọi một function async
trong vòng lặp for
.
async function doSomething(item) {
return Promise.resolve(item)
}
async function process(array) {
for (const i of array) {
await doSomething(i)
}
}
async function process(array) {
array.forEach(async i => {
await doSomething(i)
})
}
Code như vầy là tèo cmnr nha :D
Do bản thân vòng lặp for
là đồng bộ nên nó sẽ thực hiện xong toàn bộ vòng lặp trước khi các biểu thức bất đồng bộ bên trong được xử lý xong.
ES9 giới thiệu một cách giúp thực hiện điều này.
async function process(array) {
for await (const i of array) {
doSomething(i)
}
}
2. Promise.finally()
finally()
sẽ đuợc gọi sau khi một Promise hoàn thành, bất kể là fails (.catch()
) hay success (.then()
).
function process() {
process1()
.then(process2)
.then(process3)
.catch(console.log)
.finally(() => {
console.log(`it must execut no matter success or fail`)
})
}
3. Cải thiện rest - spead syntax
Thực ra mình lỡ giới thiệu hết trong phần ES6 rồi :D do quên mất là một số tính năng chỉ xuất hiện sau bản ES9.
Thôi kéo lên coi lại giúp mình nha :D
4. RegExp groups
RegExp
lúc này có thể trả về các packets thỏa điều kiện.
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 dotAll
.
đại diện cho bất kỳ ký hiệu ngoại trừ \n
- hay xuống dòng - enter
.
Thêm vào cờ s
giúp cho phép \n
.
/hello.world/.test('hello\nworld') // false
/hello.world/s.test('hello\nworld') // true
ES10 (hay ES2019)
Cũng không có quá nhiều sự thay đổi, tuy nhiên cũng khá thú vị.
1. JSON.stringify() hoạt động thân thiện hơn
Trước đó, JSON.stringify()
sẽ trả về một chuỗi string không đúng định dạng Unicode nếu đầu vào là một Unicode nhưng nằm ngoài vùng hỗ trợ.
Bây giờ thì nó sẽ trả ra một chuỗi Unicode hợp lệ dưới dạng UTF-8
.
2. Kiểu dữ liệu bigint
BigInt
- bigint
là một kiểu dữ liệu nguyên thủy mới được sử dụng để biểu diễn các số nguyên có giá trị lớn hơn 2^53
. Đây cũng là định dạng số lớn nhất mà Javascript có thể biểu diễn.
const x = Number.MAX_SAFE_INTEGER;
// 9007199254740991 - nó chính là (2^53 - 1)
const y = x + 1;
// 9007199254740992 - nó chính là (2^53)
const z = x + 2
// 9007199254740992 - vẫn là 2^53
Một số BigInt
được tạo bằng cách thêm n
vào cuối chữ số hoặc có thể gọi hàm tạo như ví dụ bên dưới
const theBiggestInt = 9007199254740991n
const alsoHuge = BigInt(9007199254740991)
// 007199254740991n
const hugeButString = BigInt('9007199254740991')
// 9007199254740991n
typeof 123 // 'number'
typeof 123n // 'bigint'
42n === BigInt(42) // true
42n == 42 // true
Tuy nhiên, khi bạn thực hiện các phép toán (+ - * / ...) thì bắt buộc phải cùng kiểu dữ liệu nha
20000000000000n / 20n
// 1000000000000n
20000000000000n / 20
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
3. String.matchAll()
String.matchAll()
sẽ trả về một iterator chứa toàn bộ các kết quả trùng khớp với đối số RegExp
được truyền vào.
const regexp = /t(e)(st(\d?))/g
const str = 'test1test2'
const array = [...str.matchAll(regexp)]
console.log(array[0])
// Array ["test1", "e", "st1", "1"]
console.log(array[1])
// Array ["test2", "e", "st2", "2"]
4. Array.flat() - Array.flatMap()
Trước đó để flatten một Array
, chúng ta hay dùng một library ngoài như Lodash. Giờ thì Javascript đã support sẵn luôn.
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]]
// Chỉ định depth thay cho mặc định
arr2.flat(2) // [1, 2, 3, 4, 5, 6]
Khi không có đối số thì depth
mặc định bằng 1 nha.
Chúng ta cũng có thể flatten toàn bộ Array
mà không cần quan tâm tới depth bằng cách truyền vào Infinity
.
const animals = [['🐕', '🐶'], ['😺', '🐈', ['😿',['🦁'], '😻']]]
const flatAnimals = animals.flat(Infinity)
console.log(flatAnimals)
// ['🐕', '🐶', '😺', '🐈', '😿', '🦁', '😻']
Còn Array.flatMap()
là sự kết hợp của 2 phương thức map()
và flat()
với depth=1
. Hay nói cách khác là mỗi giá trị của mảng sẽ được map
sang một mảng mới và flat
với depth=1
.
const animals = ['🐕', '🐈', '🐑', '🐮']
const noises = ['woof', 'meow', 'baa', 'mooo']
const mappedOnly = animals.map((animal, index) => [animal, noises[index]])
const mappedAndFlatten = animals.flatMap((animal, index) => [
animal,
noises[index],
])
console.log(mappedOnly)
// [['🐕', 'woof'], ['🐈', 'meow'], ['🐑', 'baa'], ['🐮', 'mooo']
console.log(mappedAndFlatten)
// ['🐕', 'woof', '🐈', 'meow', '🐑', 'baa', '🐮', 'mooo']
5. String.trimStart() và String.trimEnd()
String.trimStart()
loại bỏ space ở đầu chuỗi trong khi String.trimEnd()
thì xóa space ở cuối chuỗi.
const greeting = ' Hello world! '
console.log(greeting.trimStart()) // 'Hello world! '
console.log(greeting.trimEnd()) // ' Hello world!'
6. Object.fromEntries()
Phương thức này cho phép nhận một cặp pair key - value thành một object
. Nó hoạt động ngược lại so với Object.entries()
.
const entries = new Map([
['foo', 'bar'],
['baz', 42],
])
const obj = Object.fromEntries(entries)
console.log(obj) // Object { foo: "bar", baz: 42 }
ES11 (hay ES2020)
1. Promise.allSettled()
Promise hỗ trợ hai loại combinators, đó là hai phương thức tĩnh Promise.all()
và Promise.race()
. Nhưng với ES11 thì bạn có thêm phương thức Promise.allSettled()
.
Nếu Promise.all()
và Promise.race()
sẽ dừng lại khi có bất kì một Promise
nào bị rejected thì Promise.allSettled()
sẽ tiếp tục chạy hết các Promise
còn lại.
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))
)
// "fulfilled"
// "rejected"
2. Optional chaining ?
Trong quá trình làm việc, chắc hẳn bạn cũng đã quá quen thuộc với các câu lệnh kiểm tra giá trị có tồn tại hay không trước khi xử lý tiếp:
interface Car {
id: string
info: {
name: string
}
}
const car: Car | undefined = await getCar()
const isCarExist = car && car.info
if (isCarExist) {
carName = car.info.name
}
// Đoạn code sau sẽ báo lỗi khi car không tồn tại
const carInfo = car.info
Với ES11, chúng ta có thể kiểm tra một property của Object
hoặc element của Array
có tồn tại hay chưa bằng toán tử ?
carName = car?.info?.name
const carInfo = car?.info
// Hoặc kết hợp giá trị default nếu chưa tồn tại
carName = car?.info?.name || 'Default Name'
// Tương tự với Array
const arr: { name: string }[] = [{ name: 'John' }, { name: 'Jane' }, { name: 'James' }]
const name4 = arr[4]?.name || 'Default Name'
Code clean hơn nhiều rồi phải không :D
3. Toán tử check null ??
Trong Javascript, 3 giá trị 0
, null
và undefined
đều đại diện giá trị false
.
!!0 // false
!!undefined // false
!!null // false
Nhưng đôi lúc, 0
là một giá trị bình thường, chúng ta không muốn nó là false
.
const user = {
level: 0,
}
const level = user.level || 'no level'
// 0 đại diện cho false, nên ở đây JS sẽ hiển thị 'no level'
// Nếu bạn muốn hiển thị 0
const level =
user.level !== undefined && user.level !== null ? user.level : 'no level'
ES11 cung cấp cách thức khác clean hơn
const username = user.level ?? 'no level' // 0
4. Dynamic import
Như tên gọi, chúng ta có thể import
một thư viện ở bất kỳ đâu trong đoạn code.
el.onclick = () => {
import(`./greetingsModule.js`)
.then((module) => {
module.doSomthing()
})
.catch(console.error)
}
Hãy nhớ là dynamic import sẽ trả về một Promise
nhé. Bạn hoàn toàn có thể sử dụng nó kết hợp với async
/await
.
ES12 (hay ES2021)
1. Promise.any()
Promise.any()
sẽ trả về data của Promise
đầu tiên trong mảng được resolved
. Nếu tất cả Promise
đều bị rejected
, nó sẽ ném lỗi AggregateError
.
Phương thức này ngược lại với Promise.race()
.
const promise1 = Promise.reject(0)
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'))
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'))
Promise.any([promise1, promise2, promise3]).then((value) => console.log(value))
// quick
const promise4 = Promise.reject(0)
const promise5 = Promise.reject(0)
const promise6 = Promise.reject(0)
Promise.any([promise4, promise5, promise6]).then((value) => console.log(value))
// AggregateError: All promises were rejected
2. Numeric separators
Để cải thiện khả năng đọc, bổ sung mới này cho phép sử dụng _
làm dấu phân cách trong các ký tự số.
// A decimal integer literal with its digits grouped per thousand:
1_000_000_000_000
// A decimal literal with its digits grouped per thousand:
1_000_000.220_720
// A binary integer literal with its bits grouped per octet:
0b01010110_00111000
// A binary integer literal with its bits grouped per nibble:
0b0101_0110_0011_1000
// A hexadecimal integer literal with its digits grouped by byte:
0x40_76_38_6A_73
// A BigInt literal with its digits grouped per thousand:
4_642_473_943_484_686_707n
Lưu ý là nó không ảnh hưởng tới giá trị, chỉ là giúp chúng ta đọc code dễ hơn mà thôi.
3. String.replaceAll()
Một cách viết khác cho phương thức String.replace()
giúp replace toàn bộ string mà không cần sử dụng RegExp
.
const str = 'macOS is way better than windows. I love macOS.'
const newStr = str.replace('macOS', 'Linux')
console.log(newStr)
// Linux is way better than windows. I love macOS.
const newStr2 = str.replace(/macOS/g, 'Linux')
console.log(newStr2)
// Linux is way better than windows. I love Linux.
const str = 'macOS is way better than windows. I love macOS.'
const newStr = str.replaceAll('macOS', 'Linux')
console.log(newStr)
// Linux is way better than windows. I love Linux.
4. Toán tử gán có điều kiện &&=
, ||=
, ??=
Các toán tử gán logic mới được đề xuất &&=
, ||=
, ??=
giúp chúng ta có thể gán một giá trị cho một biến dựa trên một phép toán logic. Nó kết hợp phép toán logic với biểu thức gán.
let x = 10
let y = 15
x &&= y
// Tương đương if(x) x = y
x ||= y
// Tương đương if(!x) { x = y }
x ??= y
// Tương đương if(x === null || x === undefined) { x = y }
Kết
Bài dài quá rồi. Mình sẽ tổng hợp về ES2022 vào phần tới nhé.