Học ES6 (ECMAScript 2015), hướng dẫn chi tiết

Học ES6 (ECMAScript 2015), hướng dẫn chi tiết

Chúng ta sẽ cùng nhau khám phá chi tiết các kiến thức ES6 (ECMAScript 2015) mà bạn cần để làm việc hiệu quả với React. Tôi sẽ giải thích từng khái niệm, cung cấp ví dụ cụ thể và bài tập thực hành để bạn nắm vững.

Tại sao bạn nên học ES6?

Như bạn đã đề cập, React sử dụng rất nhiều tính năng mới của ES6. Việc hiểu rõ ES6 không chỉ giúp bạn viết code React một cách trơn tru mà còn mang lại nhiều lợi ích khác:

  • Code ngắn gọn và dễ đọc hơn: ES6 giới thiệu nhiều cú pháp mới giúp code trở nên súc tích và tường minh hơn.
  • Nâng cao hiệu suất: Một số tính năng mới được tối ưu hóa về hiệu suất.
  • Tính hiện đại và tương thích: ES6 là tiêu chuẩn hiện đại của JavaScript, được hỗ trợ rộng rãi bởi các trình duyệt và môi trường Node.js.
  • Dễ dàng làm việc với các thư viện và framework khác: Nhiều thư viện và framework JavaScript hiện đại cũng sử dụng các tính năng của ES6.

1. Classes (Lớp)

Trong JavaScript truyền thống (trước ES6), chúng ta sử dụng “function constructors” và “prototypes” để tạo ra các đối tượng có tính kế thừa. ES6 giới thiệu cú pháp class giúp việc định nghĩa và sử dụng các đối tượng trở nên dễ dàng và quen thuộc hơn với những người đã có kinh nghiệm với các ngôn ngữ hướng đối tượng khác (như Java, C++).

Cú pháp:

class TenLop {
  constructor(thamSo1, thamSo2) {
    this.thuocTinh1 = thamSo1;
    this.thuocTinh2 = thamSo2;
  }

  phuongThuc1() {
    // Mã thực hiện phương thức 1
    console.log("Đây là phương thức 1");
  }

  phuongThuc2(thamSo) {
    // Mã thực hiện phương thức 2
    console.log("Đây là phương thức 2 với tham số:", thamSo);
  }
}
JavaScript

Ví dụ:

class XeHoi {
  constructor(ten, mau) {
    this.tenXe = ten;
    this.mauXe = mau;
  }

  batDau() {
    console.log(`Xe ${this.tenXe} màu ${this.mauXe} đã khởi động.`);
  }

  dungLai() {
    console.log(`Xe ${this.tenXe} đã dừng lại.`);
  }
}

// Tạo một đối tượng (instance) của lớp XeHoi
const myCar = new XeHoi("Toyota", "Đỏ");
myCar.batDau(); // Output: Xe Toyota màu Đỏ đã khởi động.
myCar.dungLai(); // Output: Xe Toyota đã dừng lại.

const anotherCar = new XeHoi("Honda", "Xanh");
anotherCar.batDau(); // Output: Xe Honda màu Xanh đã khởi động.
JavaScript

Kế thừa (Inheritance): Sử dụng từ khóa extends.

class XeTai extends XeHoi {
  constructor(ten, mau, taiTrong) {
    // Gọi constructor của lớp cha (XeHoi)
    super(ten, mau);
    this.taiTrong = taiTrong;
  }

  choHang() {
    console.log(`Xe tải ${this.tenXe} đang chở ${this.taiTrong} tấn hàng.`);
  }
}

const myTruck = new XeTai("Hino", "Trắng", 5);
myTruck.batDau();   // Kế thừa phương thức từ XeHoi
myTruck.choHang(); // Phương thức riêng của XeTai
JavaScript

Bài tập 1:

  • Tạo một lớp HocSinh với các thuộc tính: ten, tuoi, lop.
  • Thêm một phương thức gioiThieu() in ra thông tin của học sinh.
  • Tạo hai đối tượng HocSinh khác nhau và gọi phương thức gioiThieu() của chúng.
  • Tạo một lớp HocSinhGioi kế thừa từ lớp HocSinh và có thêm thuộc tính diemTrungBinh. Thêm một phương thức xepLoai() để in ra “Giỏi” nếu điểm trung bình lớn hơn hoặc bằng 8, ngược lại in ra “Khá”.

2. Arrow Functions (Hàm mũi tên)

Hàm mũi tên cung cấp một cú pháp ngắn gọn hơn để viết hàm trong JavaScript. Chúng cũng có một số khác biệt quan trọng so với hàm thông thường, đặc biệt là về cách xử lý từ khóa this.

Cú pháp:

// Hàm thông thường
function tenHam(thamSo1, thamSo2) {
  return thamSo1 + thamSo2;
}

// Hàm mũi tên tương đương
const tenHamMoi = (thamSo1, thamSo2) => {
  return thamSo1 + thamSo2;
};

// Nếu chỉ có một tham số, có thể bỏ dấu ngoặc đơn
const binhPhuong = so => so * so;

// Nếu hàm chỉ trả về một biểu thức, có thể bỏ dấu ngoặc nhọn và từ khóa 'return'
const gapDoi = so => so * 2;

// Hàm không có tham số
const chao = () => console.log("Xin chào!");
JavaScript

Ví dụ:

const numbers = [1, 2, 3, 4, 5];

// Sử dụng .map() với hàm thông thường để nhân đôi mỗi phần tử
const doubledNumbersTraditional = numbers.map(function(number) {
  return number * 2;
});
console.log(doubledNumbersTraditional); // Output: [2, 4, 6, 8, 10]

// Sử dụng .map() với hàm mũi tên
const doubledNumbersArrow = numbers.map(number => number * 2);
console.log(doubledNumbersArrow); // Output: [2, 4, 6, 8, 10]

// Sự khác biệt về 'this'
class Counter {
  constructor() {
    this.count = 0;
  }

  incrementTraditional() {
    setTimeout(function() {
      // Trong hàm thông thường, 'this' thường tham chiếu đến đối tượng global (window trong trình duyệt)
      console.log("Traditional this.count:", this.count); // Output: Traditional this.count: undefined (hoặc 0 trong strict mode)
    }, 1000);
  }

  incrementArrow() {
    setTimeout(() => {
      // Trong hàm mũi tên, 'this' giữ lại giá trị của ngữ cảnh bao quanh (ở đây là đối tượng Counter)
      this.count++;
      console.log("Arrow this.count:", this.count); // Output: Arrow this.count: 1 (sau 1 giây)
    }, 1500);
  }
}

const counter = new Counter();
counter.incrementTraditional();
counter.incrementArrow();
JavaScript

Lưu ý quan trọng về this trong hàm mũi tên: Hàm mũi tên không có this riêng của nó. Giá trị của this bên trong hàm mũi tên được kế thừa từ ngữ cảnh bao quanh (lexical this). Đây là một trong những lý do chính khiến hàm mũi tên trở nên hữu ích trong các callback và xử lý sự kiện trong React.

Bài tập 2:

  • Viết một hàm mũi tên nhận vào một mảng các số và trả về một mảng mới chứa bình phương của mỗi số.
  • Viết một hàm mũi tên nhận vào một chuỗi và trả về độ dài của chuỗi đó.
  • Sử dụng phương thức forEach() của mảng và một hàm mũi tên để in ra từng phần tử của một mảng.

3. Variables (let, const, var)

ES6 giới thiệu hai từ khóa mới để khai báo biến: let và const, bên cạnh từ khóa var đã tồn tại trước đó. Sự khác biệt chính nằm ở phạm vi (scope) và khả năng gán lại giá trị.

var:
Có phạm vi hàm (function scope) hoặc phạm vi toàn cục (global scope) nếu được khai báo bên ngoài bất kỳ hàm nào.
Có thể được khai báo lại và gán lại giá trị.
Có hiện tượng “hoisting” (khai báo biến được đưa lên đầu phạm vi trước khi thực thi).

let:
Có phạm vi khối (block scope) – chỉ tồn tại trong khối lệnh (được bao bọc bởi cặp dấu ngoặc nhọn {}).
Không thể được khai báo lại trong cùng một phạm vi, nhưng có thể được gán lại giá trị.
Cũng có hoisting, nhưng không được khởi tạo trước khi dòng khai báo được thực thi (temporal dead zone).

const:
Tương tự như let về phạm vi khối.
Không thể được khai báo lại và không thể được gán lại giá trị sau khi khởi tạo.
Quan trọng: Đối với các đối tượng và mảng được khai báo bằng const, bạn vẫn có thể thay đổi các thuộc tính hoặc phần tử bên trong chúng, nhưng không thể gán một đối tượng hoặc mảng mới cho biến đó.

Ví dụ:

// var
var x = 10;
if (true) {
  var x = 20;
}
console.log(x); // Output: 20 (biến x bị ghi đè)

function testVar() {
  var y = 30;
  console.log(y); // Output: 30
}
testVar();
// console.log(y); // Lỗi: y is not defined (ngoài phạm vi hàm)

// let
let a = 10;
if (true) {
  let a = 20; // Biến 'a' này khác với biến 'a' bên ngoài khối if
  console.log(a); // Output: 20
}
console.log(a); // Output: 10

// let b = 30;
// let b = 40; // Lỗi: Identifier 'b' has already been declared

let c = 50;
c = 60;
console.log(c); // Output: 60

// const
const PI = 3.14159;
// PI = 3.14; // Lỗi: Assignment to constant variable.

const myObject = { name: "Alice", age: 30 };
myObject.age = 31; // Hoàn toàn hợp lệ
console.log(myObject); // Output: { name: "Alice", age: 31 }
// myObject = { city: "New York" }; // Lỗi: Assignment to constant variable.

const myArray = [1, 2, 3];
myArray.push(4); // Hoàn toàn hợp lệ
console.log(myArray); // Output: [1, 2, 3, 4]
// myArray = [5, 6]; // Lỗi: Assignment to constant variable.
JavaScript

Lời khuyên: Trong code hiện đại, bạn nên ưu tiên sử dụng const cho các biến mà giá trị không thay đổi, và let cho các biến mà giá trị có thể thay đổi. Hạn chế sử dụng var để tránh các vấn đề liên quan đến phạm vi và hoisting không mong muốn.

Bài tập 3:

  • Giải thích sự khác biệt giữa var, let, và const về phạm vi và khả năng gán lại giá trị.
  • Viết một đoạn code minh họa sự khác biệt về phạm vi giữa var và let trong một khối if.
  • Khai báo một biến const chứa một mảng các số. Thử thêm một phần tử mới vào mảng và in ra kết quả. Sau đó, thử gán một mảng mới cho biến const đó và xem điều gì xảy ra.

4. Array Methods like .map()

ES6 giới thiệu nhiều phương thức mới và hữu ích cho đối tượng Array, giúp thao tác với mảng trở nên dễ dàng và hiệu quả hơn. Một trong những phương thức quan trọng nhất là .map().

.map(): Phương thức này tạo ra một mảng mới bằng cách gọi một hàm callback trên mỗi phần tử của mảng ban đầu. Giá trị trả về từ callback cho mỗi phần tử sẽ trở thành phần tử tương ứng trong mảng mới.

Cú pháp:

const newArray = array.map(function(element, index, array) {
  // Trả về giá trị mới cho phần tử hiện tại
  return /* giá trị mới */;
});

// Hoặc sử dụng arrow function
const newArrayArrow = array.map((element, index, array) => /* giá trị mới */);
JavaScript
  • element: Phần tử hiện tại đang được xử lý.
  • index (tùy chọn): Chỉ số của phần tử hiện tại trong mảng.
  • array (tùy chọn): Mảng ban đầu mà .map() được gọi.

Ví dụ:

const numbers = [1, 2, 3, 4, 5];

// Nhân đôi mỗi số trong mảng
const doubled = numbers.map(number => number * 2);
console.log(doubled); // Output: [2, 4, 6, 8, 10]

// Chuyển đổi mảng các đối tượng thành mảng các tên
const users = [
  { id: 1, name: "Bob", age: 25 },
  { id: 2, name: "Charlie", age: 35 },
  { id: 3, name: "David", age: 45 },
];

const names = users.map(user => user.name);
console.log(names); // Output: ["Bob", "Charlie", "David"]

// Sử dụng index để tạo một mảng mới với thông tin vị trí
const indexedNumbers = numbers.map((number, index) => `Phần tử thứ ${index + 1}: ${number}`);
console.log(indexedNumbers); // Output: ["Phần tử thứ 1: 1", "Phần tử thứ 2: 2", ...]
JavaScript

Các phương thức mảng hữu ích khác (bạn cũng nên làm quen):

  • .filter(): Tạo một mảng mới chứa tất cả các phần tử thỏa mãn một điều kiện nhất định (được định nghĩa bởi hàm callback).
  • .reduce(): Áp dụng một hàm callback lên các phần tử của mảng (từ trái sang phải) để giảm mảng về một giá trị duy nhất.
  • .forEach(): Thực thi một hàm callback cho mỗi phần tử trong mảng (không tạo ra mảng mới).
  • .find(): Trả về giá trị của phần tử đầu tiên trong mảng thỏa mãn một điều kiện nhất định.
  • .findIndex(): Trả về chỉ số của phần tử đầu tiên trong mảng thỏa mãn một điều kiện nhất định.
  • .some(): Kiểm tra xem có ít nhất một phần tử trong mảng thỏa mãn một điều kiện nhất định hay không (trả về true hoặc false).
  • .every(): Kiểm tra xem tất cả các phần tử trong mảng có thỏa mãn một điều kiện nhất định hay không (trả về true hoặc false).

Bài tập 4:

  • Cho một mảng các số: [10, 5, 20, 8, 15]. Sử dụng .map() để tạo một mảng mới chứa các số gấp đôi.
  • Cho một mảng các đối tượng sản phẩm với thuộc tính ten và gia. Sử dụng .map() để tạo một mảng mới chỉ chứa tên của các sản phẩm.
  • Cho mảng số ban đầu ở câu 1, sử dụng .filter() để tạo một mảng mới chỉ chứa các số lớn hơn 10.
  • Cho mảng số ban đầu ở câu 1, sử dụng .reduce() để tính tổng của tất cả các số trong mảng.

5. Destructuring (Phân rã cấu trúc)

Destructuring là một tính năng mạnh mẽ cho phép bạn trích xuất các giá trị từ mảng hoặc thuộc tính từ đối tượng vào các biến riêng biệt một cách ngắn gọn.

Phân rã mảng:

const numbers = [1, 2, 3];
const [first, second, third] = numbers;
console.log(first, second, third); // Output: 1 2 3

const [a, , c] = numbers; // Bỏ qua phần tử thứ hai
console.log(a, c); // Output: 1 3

const [head, ...rest] = numbers; // Lấy phần tử đầu tiên và phần còn lại
console.log(head); // Output: 1
console.log(rest); // Output: [2, 3]

const [x, y, z = 0] = [10, 20]; // Giá trị mặc định cho z
console.log(x, y, z); // Output: 10 20 0
JavaScript

Phân rã đối tượng:

const person = { name: "Alice", age: 30, city: "New York" };
const { name, age, city } = person;
console.log(name, age, city); // Output: Alice 30 New York

const { name: ten, age: tuoi } = person; // Đặt tên biến khác
console.log(ten, tuoi); // Output: Alice 30

const { country = "USA" } = person; // Giá trị mặc định nếu
JavaScript

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *