Tổng hợp kiến thức ES6
Bài đăng này đã không được cập nhật trong 5 năm
- 1. let, const và block scoping
- 2. Arrow Functions
- 3. Default Function Parameters
- 4. Spread / Rest Operator
- 5. Object Literal Extensions
- 6. Octal và Binary Literals
- 7. Array và Object Destructuring
- 8. Super in Objects
- 9. Template Literal và Delimiters
- 10. for...of vs for...in
- 11. Map và WeakMap
- 12. Set và WeakSet
- 13. Classes trong ES6
- 14. Symbol
- 15. Iterators
- 16. Generators
- 17. Promises
1. let, const và block scoping
let
cho phép bạn tạo khởi tạo khai báo với phạm vi trong block được gọi là block scope. Nó dùng để thay thế var
, cái mà trước đây phạm vi của nó là function scope, khuyễn khích nên sử dụng biễn trong block scope (let
or const
) trong ES6.
var a = 2;
{
let a = 3;
console.log(a); // 3
let a = 5; // TypeError: Identifier 'a' has already been declared
}
console.log(a); // 2
Một hình thức khai báo khác của block-scoped là const
, sử dụng khi tạo hằng số. Trong ES6, một const
được sử dụng khi tham chiếu liên tục đến một giá trị. Cách nói khác, giá trị của Object
và Array
có thể thay đổi nhưng không được gán lại biến. Ở đây là một ví dụ đơn giản:
{
const b = 5;
b = 10; // TypeError: Assignment to constant variable
const arr = [5, 6];
arr.push(7);
console.log(arr); // [5,6,7]
arr = 10; // TypeError: Assignment to constant variable
arr[0] = 3; // value is mutable
console.log(arr); // [3,6,7]
}
Một số điều cần ghi nhớ:
- Hoisting (là khái niệm chỉ việc mọi khai báo biến) của
let
vàconst
khác nhau cơ bản ở hoisting của biến và hàm. Cảlet
vàconst
đều hoisted, nhưng không thể được truy cập trước khi khai vào bời vì Temporal Dead Zone let
vàconst
được sử dụng trong phạm vi gần dấu mở ngoặc nhất cho tới dấu đóng ngoặc.- Khi fix một giá trị cụ thế không thay đổi suốt chương trình nên đặt tên dưới dạng caplock (vd:
const PI = 3.14
) const
phải được định nghĩa khi khai báo nó.- Luôn sử dụng
const
thay vìlet
, trừ khi bạn muốn gắn lại biến.
2. Arrow Functions
Arrow functions là dạng viết ngắn gọn cho một hàm trong ES6. Một arrow function được hình thành bơi một danh sách các parameter ( ... )
, theo sau là =>
để đánh đấu sau nó là body function. Đối với hàm chỉ có một parameter thì có thể bỏ qua cặp dấu ngoặc tròn.
// Classical Function Expression
function addition(a, b) {
return a + b;
}
// Implementation with arrow function
const addition = (a, b) => a + b;
// With single argument, no parentheses required
const add5 = a => 5 + a;
Lưu ý ví dụ ở bên trên, addition
arrow function được implement với "concise body" có nghĩa là nó khong cần phải đặt trong cặp dấu { }
sau =>
vì phần body của nó chỉ có một câu lệnh.
Dưới đây là một ví dụ sử dụng "block body" thông thường.
const arr = ["apple", "banana", "orange"];
const breakfast = arr.map(fruit => {
return fruit + "s";
});
console.log(breakfast); // ['apples', 'bananas', 'oranges']
Kìa! Còn nhiều hơn thế...
Arrow functions không chỉ làm code của bạn ngắn hớn. Chúng còn có liên kết chặt chẽ với việc binding this
.
Mỗi function trong Javascript đều định nghĩa ngữ cảnh của nó là từ khoá this
. Việc sử dụng arrow function có thể làm thay đổi ngữ cảnh này, nó sẽ thảnh đổi this
trỏ vào ngữ cảnh gần nó nhất. Hãy kiếm tra đoạn ví dụ dưới đây:
function Person() {
// The Person() constructor defines `this` as an instance of itself.
this.age = 0;
setInterval(function growUp() {
// In non-strict mode, the growUp() function defines `this`
// as the global object, which is different from the `this`
// defined by the Person() constructor.
this.age++;
}, 1000);
}
var p = new Person();
Trong ECMAScript 3/5, vấn đề này đã được khắc phục bằng cách gán giá trị trong this
cho một biến có thể được đóng lại.
function Person() {
const self = this;
self.age = 0;
setInterval(function growUp() {
// The callback refers to the `self` variable of which
// the value is the expected object.
self.age++;
}, 1000);
}
Như đã đề cập ở trên, các arrow function nắm bắt giá trị này của ngữ cảnh kèm theo gần nhất, do đó đoạn mã sau hoạt động như mong đợi, ngay cả với các arrow function lồng nhau.
function Person() {
this.age = 0;
setInterval(() => {
setTimeout(() => {
this.age++; // `this` properly refers to the person object
}, 1000);
}, 1000);
}
let p = new Person();
Read more about 'Lexical this' in arrow functions here
3. Default Function Parameters
ES6 cho phép bạn set giá trị mặc định cho parameters khi khai báo hàm ES6 allows you to set default parameters in function definitions. Dưới đây là một minh hoạ đơn giản:
const getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850
4. Spread / Rest Operator
...
là toán tử được gọi là spread hoặc rest operator, tuỳ thuộc vào cách thức và nơi chúng được sử dụng.
Khi sử dụng bất kỳ một iterable nào chúng đều chia như "spread" thành các phần tử riêng lẻ:
const makeToast = (breadType, topping1, topping2) => {
return `I had ${breadType} toast with ${topping1} and ${topping2}`;
};
const ingredients = ["wheat", "butter", "jam"];
makeToast(...ingredients);
// "I had wheat toast with butter and jam"
makeToast(...["sourdough", "avocado", "kale"]);
// "I had sourdough toast with avocado and kale"
Cũng có thể sử dụng spread để lấy shaping từ nột đối tượng mới từ một hoặc nhiều đối tường khác:
const defaults = { avatar: "placeholder.jpg", active: false };
const userData = { username: "foo", avatar: "bar.jpg" };
console.log({ created: "2017-12-31", ...defaults, ...userData });
// {created: "2017-12-31", avatar: "bar.jpg", active: false, username: "foo"}
Mảng cũng có thể như vậy:
const arr1 = [1, 2, 3];
const arr2 = [7, 8, 9];
console.log([...arr1, 4, 5, 6, ...arr2]); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Cách sử dụng phổ biến khác của ...
là tập hợp các đối số của mảng, điều này được gọi là toán tử "rest"
function foo(...args) {
console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
5. Object Literal Extensions
ES6 cho phép khai báo các đối tượng bằng cách cung cấp cú pháp ngắn gọn để khởi tạo các thuộc tính từ các biến và xác định các phương thức hàm. Nó cũng có thể có computed property keys trong một object literal.
function getCar(make, model, value) {
return {
// with property value shorthand
// syntax, you can omit the property
// value if key matches variable
// name
make, // same as make: make
model, // same as model: model
value, // same as value: value
// computed values now work with
// object literals
["make" + make]: true,
// Method definition shorthand syntax
// omits `function` keyword & colon
depreciate() {
this.value -= 2500;
}
};
}
let car = getCar("Kia", "Sorento", 40000);
console.log(car);
// {
// make: 'Kia',
// model:'Sorento',
// value: 40000,
// makeKia: true,
// depreciate: function()
// }
6. Octal và Binary Literals
ES6 có hỗ trợ mới cho các số bát phân và nhị phân.
Một số bắt đầu với 0o
hoặc 0O
sẽ được convert sang hệ bát phân. Hãy xem ví dụ dưới đây:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 0b or 0B for binary
console.log(bValue); // 2
7. Array và Object Destructuring
Destructuring giúp tránh sự cần thiết của các biến tạm thời khi làm việc với đối tượng và mảng.
function foo() {
return [1, 2, 3];
}
let arr = foo(); // [1,2,3]
let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3
function getCar() {
return {
make: "Tesla",
model: "g95",
metadata: {
vin: "123abc",
miles: "12000"
}
};
}
const { make, model } = getCar();
console.log(make, model); // Tesla g95
const {
make,
metadata: { miles }
} = getCar();
console.log(make, miles); // Tesla 12000
8. Super in Objects
ES6 sử dụng phương thức super
trong (classless) đối tượng với prototypes. Theo dõi ví dụ dưới đây:
const parent = {
foo() {
console.log("Hello from the Parent");
}
};
const child = {
foo() {
super.foo();
console.log("Hello from the Child");
}
};
Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
// Hello from the Child
9. Template Literal và Delimiters
ES6 giới thiệu một cách dễ dàng hơn để thêm các phép interpolations được đánh giá tự động.
`${ ... }`
được sử dụng để hiển thị các biến.`
backtick được sử dụng như dấu phân cách.
let user = "Kevin";
console.log(`Hi ${user}!`); // Hi Kevin!
10. for...of vs for...in
for...of
lặp qua các đối tượng lặp, chẳng hạn như mảng.
const nicknames = ["di", "boo", "punkeye"];
nicknames.size = 3;
for (let nickname of nicknames) {
console.log(nickname);
}
// di
// boo
// punkeye
for...in
lặp qua các thuộc tính enumerable của từng đối tương. Chẳng hạn:
const nicknames = ["di", "boo", "punkeye"];
nicknames.size = 3;
for (let nickname in nicknames) {
console.log(nickname);
}
// 0
// 1
// 2
// size
11. Map và WeakMap
ES6 giới thiệu bộ cấu trúc mới gọi là Map
và WeakMap
. Bầy giờ, chúng ta hoàn toàn có thể sử dụng map trong Javascript. Thực thế tất cả các object đều có thể xem như một Map
.
Một đối tượng trong Map
được tạo bởi key (luôn luôn là strings) và values, mọi value (cả objects và primitive values) có thể được sử dụng làm một key hoặc một value. Hãy nhìn vào đoạn code này:
const myMap = new Map();
const keyString = "a string",
keyObj = {},
keyFunc = () => {};
// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");
myMap.size; // 3
// getting the values
myMap.get(keyString); // "value associated with 'a string'"
myMap.get(keyObj); // "value associated with keyObj"
myMap.get(keyFunc); // "value associated with keyFunc"
WeakMap
Một WeakMap
là một Map với các key với tham chiếu yếu, không ngăn chặn các keys của nó từ garbage-collected. Điều đó có nghĩa là bạn không phải lo lắng về rò rỉ bộ nhớ.
Một số lưu ý trong WeakMap
trái ngược với Map
mọi key phải là một đối tượng.
Một WeakMap
chỉ có bốn phương thức delete(key)
, has(key)
, get(key)
và set(key, value)
.
const w = new WeakMap();
w.set("a", "b");
// Uncaught TypeError: Invalid value used as weak map key
const o1 = {},
o2 = () => {},
o3 = window;
w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);
w.get(o3); // undefined, because that is the set value
w.has(o1); // true
w.delete(o1);
w.has(o1); // false
12. Set và WeakSet
Set objects là tập hợp các giá trị duy nhất. Những giá trị trùng lặp sẽ bị bỏ qua, vì tất cả các giá trị trong Set phải là duy nhất. Các giá trị có thể là kiểu nguyên thủy hoặc tham chiếu đối tượng.
const mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add("strings");
mySet.add({ a: 1, b: 2 });
Bạn cũng có thể lặp qua từng phần tử trong Set bằng cách sử dụng phương thức forEach
hoặc for...of
.
mySet.forEach(item => {
console.log(item);
// 1
// 2
// 3
// 'strings'
// Object { a: 1, b: 2 }
});
for (let value of mySet) {
console.log(value);
// 1
// 2
// 3
// 'strings'
// Object { a: 1, b: 2 }
}
Sets cũng có các phương thức delete()
và clear()
.
WeakSet
Tương tự WeakMap
, WeakSet
đối tượng cho phép bạn lưu trữ yêu objects ở trong tập hợp. Một đối tượng ở trong WeakSet
chỉ xảy ra một lần; nó là duy nhất trong tập của Weakset.
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false, foo has not been added to the set
ws.delete(window); // removes window from the set
ws.has(window); // false, window has been removed
13. Classes trong ES6
ES6 giới thiệu cú pháp class mới. Cần chú ý răng Class trong ES6 không phải là mô hình thừa kế hướng đối tượng mới. Chúng chỉ đóng vai trò là đường tổng hợp so với kế thừa dựa trên nguyên mẫu hiện có của JavaScript.
Class trong ES6 chỉ sử dụng cú pháp mới để làm việc với prototypes và cấu trúc hàm mà chúng sử dụng trong ES5.
Định nghĩa các hàm sử dụng từ khoá static
để implement static/class functions trong class.
class Task {
constructor() {
console.log("task instantiated!");
}
showId() {
console.log(23);
}
static loadAll() {
console.log("Loading all tasks..");
}
}
console.log(typeof Task); // function
const task = new Task(); // "task instantiated!"
task.showId(); // 23
Task.loadAll(); // "Loading all tasks.."
Thừa kế và super trong classes
Theo dõi đoạn code dưới đây:
class Car {
constructor() {
console.log("Creating a new car");
}
}
class Porsche extends Car {
constructor() {
super();
console.log("Creating Porsche");
}
}
let c = new Porsche();
// Creating a new car
// Creating Porsche
extends
cho phép class con thừa kế từ class cha trong ES6. Điều quan trọng cần lưu ý là hàm tạo dẫn xuất phải gọi super()
.
Tuy nhiên bạn cũng có thể gọi phương thức của class cha trong class con bằng cách sử dụng phương thức super.parentMethodName()
Một vài cần lưu ý:
- Khai báo Class không phải hoisted. Trước tiên bạn cần khai báo lớp của mình và sau đó truy cập vào nó, nếu không nó sẽ ném ra ReferenceError.
- Không cần sử dụng từ khoá
function
khi định nghĩa functions bên trong một định nghĩa class.
14. Symbol
Một Symbol
là một kiểu dữ liệu duy nhất và bất biến được giới thiệu trong ES6. Mục đích của symbol là tạo ra một mã định danh duy nhất nhưng bạn không bao giờ có thể có quyền truy cập vào mã định danh đó.
Dưới đây là cách tạo symbol:
const sym = Symbol("some optional description");
console.log(typeof sym); // symbol
Chú ý rằng không được sử dụng new
với Symbol(…)
.
Nếu một symbol được sử dụng như một property/key của một đối tượng, nó được lưu trữ theo cách đặc biệt mà ở trong property không hiển thị trong bảng liệt kê của các thuộc tính đối tượng..
const o = {
val: 10,
[Symbol("random")]: "I'm a symbol"
};
console.log(Object.getOwnPropertyNames(o)); // val
Để lấy một thuộc tính ký hiệu đối tượng, sử dụng Object.getOwnPropertySymbols(o)
15. Iterators
Một iterator cho phép lặp từng phần tử từ một tập trong cùng một thời điểm, trong khi theo dõi vị trí hiện tại của nó trong chuỗi đó. Iterators cung cấp phương thức next()
sẽ trả về item tiếp theo trong dãy. Phương thức này sẽ trả về một đối tượng với hai thuộc tính: done và value.
ES6 có Symbol.iterator
chỉ định iterator mặc định cho một đối tượng. Bất cứ khi nào một đối tượng cần duyệt vòng lặp (tương tự như sử dụng vòng lặp for..of), phương thức @@iterator được gọi không có đối số, và iterator trả về được sử dụng để thu được các giá trị được lặp lại.
Hãy nhìn vào mảng dưới đây để hiểu về interator:
const arr = [11, 12, 13];
const itr = arr[Symbol.iterator]();
itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }
itr.next(); // { value: undefined, done: true }
Chú ý bạn có thể customer interator bơi obj[Symbol.iterator]()
với định nghĩa đối tượng.
16. Generators
Generator functions là một tính năng mới trong ES6 nó cho phép một hàm có thể tạo ra nhiều giá trị theo thời gian bằng cách trả về một đối tượng có thể được lặp đi lặp lại để kéo các giá trị từ hàm một giá trị tại một thời điểm.
Một generator function trả về một ** đối tượng iterable** khi nó được gọi.
Nó được viết bằng cách sử dụng cú pháp mới *
syntax cũng như từ khoá yield
được giới thiệu trong ES6.
function* infiniteNumbers() {
let n = 1;
while (true) {
yield n++;
}
}
const numbers = infiniteNumbers(); // returns an iterable object
numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }
Mỗi lần yield được gọi, giá trị yielded trở thành giá trị tiếp theo trong chuỗi.
17. Promises
ES6 có hỗ trợ riêng cho các promises. Một promise là một đối tượng mà nó sẽ chờ cho một hoạt động không đồng bộ hoàn thành, và khi toán tử hoàn thành, promise sẽ trả về hoặc là fulfilled(resolved) hoặc là rejected.
Cách tiêu chuẩn để tạo một promise là sử dụng cấu trúc new Promise()
và nó sẽ nhận hai parameter. Tham số đầu tiên (thường được đặt tên là resolve
) là một hàm được gọi khi xử lý xong và thành công, tham số thứ hai (thường được đặt tên là reject
) được gọi khi xử lý xong và và thất bại:
const p = new Promise((resolve, reject) => {
if (/* condition */) {
resolve(/* value */); // fulfilled successfully
} else {
reject(/* reason */); // error, rejected
}
});
Mọi promise đều có một phương thức là then
nó nhận một cặp của callbacks.
p.then(
val => console.log("Promise Resolved", val),
err => console.log("Promise Rejected", err)
);
Giá trị được return bởi then
trước sẽ chuyển vào parameter của then
sau. Hãy nhìn ví dụ dưới đây:
const hello = new Promise((resolve, reject) => {
resolve("Hello");
});
hello
.then(str => `${str} World`)
.then(str => `${str}!`)
.then(str => console.log(str)); // Hello World!
Khi trả về một promise, giá trị resolved của promise sẽ được truyền vào call back tiếp theo để nối chúng lại với nhau. Đây là một kỹ thuật đơn giản để tránh "callback hell".
const p = new Promise((resolve, reject) => {
resolve(1);
});
const eventuallyAdd1 = val =>
new Promise((resolve, reject) => {
resolve(val + 1);
});
p.then(eventuallyAdd1)
.then(eventuallyAdd1)
.then(val => console.log(val)); // 3
All rights reserved