Tìm hiểu ECMAScript 6 - những Điều hay ho của ES6 (phần 1)

Nodejs là một cách để chạy javascript trên phía server và rất thích hợp với trình duyệt Chrome. Hiện đang là cái tên rất hot hiện nay trong lĩnh vực phát triển web.

Node js hiện đã hỗ trợ ECMAScript6 (ES6) với cú pháp gọn gàng và mạnh mẽ hơn. Sau đây là một số những tiện ích thú vị mà ES6 đã thay đổi so với "người đàn anh" ES5 của mình.

1.Khai báo let

ES6 cho phép khai báo một biến trong 1 block code, đóng vai trò như khai báo một biến cục bộ

if(true) {
  let x = 1;
  console.log(x); // in ra: "1"
}
console.log(x);  // undefined x do x chỉ được khai báo trong khối lệnh if() { ... }

2.Khai báo const

Cho phép định nghĩa một biến toàn cục hoặc biến cục bộ, tuy nhiên trong một phạm vi một khối lệnh thì chỉ được phép khai báo biến một lần. Nếu định nghĩa lại một biến x chẳng hạn, khi đó sẽ xuất hiện lỗi. Ví dụ:

const x = 1;  // khai báo lần thứ nhất thành công.
const x = 0;  // khai báo lần thứ hai sinh lỗi vì cùng trong khu vực biến toàn cục(hoặc cùng khối lệnh).
if(true) {
  const x = 2; // khai báo thành công do đang trong khối lệnh if.
  const y = 1; // khai báo thành công y trong khối lệnh if.
  console.log(x,y); // in ra: "2 1" vì khai báo thành công.
}
// Nếu tiếp tục khai báo y sau đây
const y = 2; // vì trong phạm vi toàn cục chưa có y được khai báo nên không sinh lỗi.

const có cách dùng tương đối giống với var trong ES5 hoặc js thông thường. Tuy nhiên khác về phạm vi sử dụng.

3.Trả về nhiều giá trị đồng thời trong 1 hàm

ES6 cho phép trả về đồng thời nhiều giá trị từ 1 function. Điều này mang lại nhiều tiện lợi cho coder khi tính toán ra cả dãy giá trị nhưng lại không muốn gộp chúng lại về một đối tượng. Mà đơn giản chỉ cần trả về tất cả giá trị muốn lấy. Ví dụ như:

function multi_values() {
  return [1, 2, 3, 4, 5, 6];
}
// khi muốn gọi giá trị từ hàm trả về
[x1, x2, , , x5, x6] = multi_values();
console.log(x6);  // in ra: "6"

chỉ cần khai báo với [] là bạn đã có thể tận hưởng sự tiện lợi từ sự trả về nhiều giá trị của ES6. Và các giá trị trả về ở vị trí tương ứng với biến mà bạn gọi trong "mảng" đó.

4.Các tham số mặc định trong hàm

ES6 cho thấy sự thông minh trong cách nhận giá trị tham số đầu vào của một function. Bằng cách tự động điền giá trị tham số đầu vào theo thứ tự truyền vào tương ứng. Sau đây là vài ví dụ cụ thể:

function  default1 (x = 1, y = 2, z = 3) {
  console.log(x, y, z);
}
default1(5, 6); // hàm console.log sẽ in ra: "5 6 3"

Một ví dụ khác để thể hiện cách khai báo không định nghĩa một giá trị truyền vào khi gọi hàm:

function default2 (x = 1, y = 2, z = 3) {
  console.log(x, y, z);
}
default2(undefined, 6, 7); // in ra: "1 6 7"

5.Sử dụng "..."

"..." trong ES6 có vai trò như định nghĩa một mảng động khi truyền vào tham số cho một function. Ví dụ dưới đây sẽ phần nào minh họa cho cách mà ES6 sử dụng ưu thế này, nhận một mảng ở tham số đầu vào của một function

function three_dot1 (...args) {
  console.log(args.length); // in ra "4"
  console.log(args);  // in ra "1 2 3 4"
}
three_dot1(1, 2, 3, 4);

bằng cách trên có thể khai báo một hàm gồm nhiều giá trị mà khi gọi hàm có thể truyền vào bao nhiêu biến tùy ý.

Nó có thể dùng kết hợp với cách truyền tham số thông thường một cách linh hoạt:

function three_dot2 (a, b, ...args) {
  console.log(args.length); // in ra "2"
  console.log(args); // in ra "3 4"
}
three_dot2(1, 2, 3, 4);

6.Dùng `` trong ES6

bằng cách sử dụng cặp kí tự ` ` khi viết/gán một chuỗi string, khi xuống dòng trong khi gán chuối trên, sẽ được nhận như là một kí tự '\n trong chuỗi Ví dụ:

const a = `
 this is line 1
 this is line 2
 `;

thì chuỗi a nhận được tương đương với

var a = ('\n this is line 1 \n this is line 2 \n');

Bên cạnh đó, có thể dùng kí tự đặc biệt này để in ra biến mà không cần cộng các chuỗi string và các biến lại với nhau như javascript thông thường nữa: ví dụ: bình thường ta có thể viết:

var line1 = 'Line 1 ';
console.log('content of line1 is: ' + line1);

thì mới có thể gắn chuỗi và biến vào một dòng text. tuy nhiên, với ES6 ta có thể viết lại:

const line1 = 'Line 1 ';
console.log(`content of line1 is: ${line1}`);

cách viết của ES6 trông gọn gàng và dễ hiểu hơn để phân biệt được đâu là chuỗi đâu là biến(có dấu ${} báo hiệu cho biến) và kết hợp với cách xuống dòng không cần kí tự \n làm cho chuỗi trở nên ngắn gọn hơn.

7.Cách dùng mũi tên => thay cho từ khóa function

Trong cú pháp của ES6 dù vẫn chấp nhận từ khóa function tuy nhiên, khi sự chuẩn hóa của ES6 vẫn là =>. Đặc biệt, khi sử dụng các biến this trong function cần biên dịch sang ES5 sẽ bị lỗi khi gặp chúng, lúc đó => cho thấy sự cần thiết của mình.

Một ví dụ đơn giản và nhanh chóng trong cách viết sử dụng =>:

const sum = (x, y) => x + y;
console.log(sum(2000, 15)); // in ra "2015"

Cách dùng khác một chút:

sum(a, b) {
  console.log(a() + b());
}
sum(a => 1 + 1, b => 2 + 2); // in ra: "6"

8.Sử dụng for of

Với vòng lặp for trong ES6 , ta có thể viết

const array = [1, 2, 3, 4, 5, 6];

for(var i of array) {
  console.log(i);
}

9.Keyword yield và cú pháp function*()

Một đặc trưng riêng của ES6 mà người ta gọi là JavaScript Generators . yield và cú pháp function*() là nguyên nhân khiến người ta gọi bằng tên gọi này. Cách dùng này cho phép gọi các giá trị trả về từ từng yield như một phần tử của mảng và cả function như là một mảng. Sau đây là so sánh hai cách dùng giống nhau để thấy được tác dụng của chúng:

function collection1()
{
    return [1, 2, 3, 4];
}

var collection1 = collection1();

for(var i = 0; i < collection.length; i++)
{
    console.log(collection1[i]);
}

còn đây là sự kết hợp của ES6:

function* collection2() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

for(var i of collection2()) {
  console.log(i);
}

10.Javascript Set Object

Cách Set trong ES6 làm việc như cách lưu trữ thành một mảng, tuy nhiên là mảng một chiều, tức là nó chỉ cho phép lưu mình key của phần tử. Tuy nhiên đó chỉ là ở bước khởi tạo key còn nếu muốn gán value cho phần tử thì vẫn có thể thêm được ( bằng phương thức khác, có thể là map).

Một số minh họa cho cách làm việc với set:

var set = new Set();

// Add key
set.add({x: 1});
set.add(2);
set.add("text");

console.log(set.has("text")); // kiếm tra xem có key == 'text' hay không.
set.delete(1); // xóa một key

var set_1 = new Set([1, 2, 3, 4]);  // khởi tạo một set bằng dữ liệu là một mảng
console.log(set_1.size); // in ra size của 1 set "5"

var set_2 = new Set(set_1.values()); // sao chép các giá trị key từ một set khác.

11.Javascript Map Object

Javascript Map là một tập với cặp keyvalue duy nhất. Với các cặp keyvalue có thể là các đối tượng hoặc là các kiểu dữ liệu cơ bản.

So với kiểu mảng 2 chiều thì có thể lưu trữ nhiều values nhưng đối với Map thì chỉ có thể lưu quan hệ một - một giữa keyvalue. Đó là điểm khác biệt cơ bản của Map với 2D Arrays ( mảng hai chiều).

Một Object Set chỉ có thể lưu mình keys, còn Map có thể lưu keysvalues theo từng cặp.

Dưới đây là đoạn code minh họa cho Map trong ES6:

var map = new Map(); // khởi tạo Map

map.set(1, 1000); // khởi tạo một cặp key - value
map.set(1, 5000); // ghi đè lên một đối tượng.

console.log(map.has(1)); // kiểm tra xem có đối tượng key == 1 hay không.

console.log(map.get(1)); // in ra value của đối tượng có key == 1

for(var i of map) {
  console.log(i); // in ra dãy keys của map
}

map.delete(1); // xóa key == 1

map.clear(); // xóa toàn bộ key

var map_1 =  new Map([2, 10], [3, 5]);  // Khởi tạo theo kiểu mảng.
console.log(map_1.size()); // in ra kích thước/ số cặp key - value của map.

12.WeakMap

Một phương thức cũng được ES6 hỗ trợ. Nghe có vẻ giống với map nhưng "yếu" (weak) hơn map ở trên. Có gì khác nhau giữa mapweekmap hay không? Câu trả lời là:

var map = new Map();
var weakmap = new WeakMap();

(() => {
  var a = { x: 1 };
  var b = { y: 2 };

  map.set(a, 11);
  weakmap.set(b, 22);
})()

Trong hai phép gán trên, mặc dù vẫn có hai cặp key - value:

(1, 11)(2, 22)

Tuy nhiên, cách tạo ra cặp key - value này lại khác nhau.

Đối với map thì ta đã biết, chỉ việc add value vào từ pointer có key == 1 (dùng map ở trên)

Còn đối với weakmap nó xóa pointer b = { y: 2 } được tạo ra trước đó, sau đó khởi tạo pointer mới của weakmap.

Đó là lí do vì sao liên kết của weakmap "yếu hơn" của map.

Còn nữa, weakmap không thể nhận những kiểu giá trị nguyên thủy:

map.set(1, 6); // It's OK
weakmap.set(1, 5); // error: invalid type

// Hãy set giá trị theo kiểu mảng
var weak_map1 = new WeakMap([1, 3], [2, 4]);

Và một đặc điểm nữa của weakmap là không hỗ trợ một số method:

console.log(weakmap.size);  // undefined

for(var i of weakmap) {
  console.log(i);  // không chạy
}

weakmap.clear(); // Nhưng lại có thể xóa được

Câu hỏi đặt ra là: Tại sao lại dùng weakmap ? Lý do đưa ra là

Thứ nhất là lợi ích về nâng cao quá quá trình cho bộ thu gom rác dữ liệu. Tức là, những thứ dư thừa hoặc không dùng đến sẽ được thu gom vào thùng rác dữ liệu. Tuy nhiên, với weakmap thì sẽ hiệu quả hơn thế. Bởi vì, trong weakmap chỉ có một mối quan hệ là key -> value. Và key/value có thể được đưa vào thùng rác (garbage collected) trong khi weakmap vẫn tồn tại. So sánh với kiểu Array thì Array sẽ không bị di chuyển vào thùng rác chừng nào Array vẫn còn tồn tại và chưa có lệnh xóa. Điều này yêu cầu phải có sự minh bạch trong quản lý vòng đời đối tượng, hoặc các kết quả đơn giản trong lỗ hổng bộ nhớ ( memory leak).

Ví dụ:

Lưu trữ dữ liệu của jQuery, dữ liệu không bao giờ được di chuyển vào thùng rác khi nó không bị nulled out. Bởi vì nó được lưu trữ với mối ràng buộc mạnh trong cách tổ chức các items. Còn đối với weakmap nó có thể liên kết dữ liệu với một phần tử và khi dữ liệu bị destroy thì phần tử cũng thoát khỏi bộ nhớ, nói cách khác được đưa vào thùng rác(xóa).

Trong bài viết này, tôi đã đề cập đến một số cú pháp và cách viết mới lạ cơ bản của ES6, nhận thấy ES6 có sự thay đổi và trở nên gọn nhẹ dễ hiểu hơn so với ES5. Bên cạnh đó cũng có một số lai tạo trong cú pháp với các ngôn ngữ khác.

Tài liệu tham khảo:

ES6 tutorial

http://mahpahh.com/viet-ung-dung-angular-1-voi-es6/