ECMAScript và những thay đổi của ECMAScript 6

ECMAScript và những thay đổi của ECMAScript 6

1. ECMAScript, định nghĩa và lịch sử đến nay

  • Nói một cách đơn giản thì ECMAScript là một ngôn ngữ đặc tả được nhiều ngôn ngữ sử dụng bên phía client trên World Wide Web
  • Chính vì được áp dụng bởi rất nhiều các ngôn ngữ client-side nên ES6 khá nổi tiếng. Ban đầu ECMAScript được gọi là ECMA-262 và dần dần sau nhiều bản cập nhật thì hiện tai ECMAScript có tên gọi là ECMAScript 6 (ES6).

Mình sẽ không đi sâu vào lịch sử hình thành và xin phép đưa ra quá trình phát triển của ECMAScript ở đây.

Phiên bản Ngày công bố Những thay đổi so với bản cập nhật trước Tác giả
1 6/1997 Phiên bản đầu tiên Guy L. Steele Jr.
2 6/1998 Những thay đổi để giúp cho đặc tả phù hợp với tiêu chuẩn quốc tế ISO / IEC 16262 Mike Cowlishaw
3 12/1999 Thêm các biểu thức thông thường, xử lý chuỗi tốt hơn, các câu lệnh kiểm soát mới, xử lý ngoại lệ / bắt lỗi, định nghĩa chặt chẽ hơn về lỗi, định dạng cho các số đầu ra và các cải tiến khác Mike Cowlishaw
4 Phiên bản thứ 4 đã bị bỏ do có liên quan đến sự phức tạp giữa cấu trúc của các ngôn ngữ. Nhiều tính năng được đề xuất cho phiên bản thứ 4 đã bị loại bỏ hoàn toàn.
5 12/2009 Thêm "strict mode", đó là một tập hợp con nhằm cung cấp kiểm tra lỗi kỹ lưỡng hơn và tránh các cấu trúc bị lỗi. Làm rõ nhiều sự mơ hồ trong đặc tả phiên bản thứ 3 và thực hiện thích ứng với các hành động trong thế giới thực mà có sự khác biệt so với đặc tả đó. Thêm một số tính năng mới, chẳng hạn như getters và setters, hỗ trợ thư viện cho JSON, và phản ánh đầy đủ hơn về các thuộc tính của đối tượng. Pratap Lakshman, Allen Wirfs-Brock
5.1 6/2011 Giống phiên bản thứ 2, phiên bản 5.1 nhắm giúp cho ECMAScript phù hợp với tiêu chuẩn quốc tế mới ISO/IEC 16262:2011. Pratap Lakshman, Allen Wirfs-Brock
6 6/2015 Phiên bản thứ 6, ban đầu được gọi là ECMAScript 6 (ES6) nhưng sau đó đổi tên thành ECMAScript 2015 (ES2015), ở phiên bản này đã được bổ sung thêm cú pháp mới quan trọng cho việc viết các ứng dụng phức tạp, bao gồm classes và modules, nhưng định nghĩa chúng bằng ngữ nghĩa theo các điều khoản giống như ECMAScript 5 strict mode. Các tính năng mới khác bao gồm các vòng lặp và for/of của vòng lặp, khởi tạo kiểu Python và biểu thức khởi tạo, các chức năng arrow, dữ liệu nhị phân, nhập mảng, promises, số và cải tiến phép toán, reflection và Lập trình meta cho các đối tượng ảo Allen Wirfs-Brock
7 6/2016 Phiên bản thứ bảy, còn được gọi là ECMAScript 2016, nhằm tiếp tục các chủ đề cải cách ngôn ngữ, cách ly mã nguồn, kiểm soát hiệu ứng và công cụ thư viện / công cụ từ ES2015, bao gồm hai tính năng mới: toán tử lũy thừa và includes nguyên mảng Brian Terlson

Trên đây là một vài chi tiết liên quan đến ECMAScript, giờ chúng ta cùng đến với những thay đổi của ECMAScript nhé.

2. Những thay đổi của phiên bản ES6


2.1 Đầu tiên là về việc khai báo contants

Hỗ trợ các hằng số, nghĩa là các biến không thể được gán lại nội dung mới. Lưu ý: điều này chỉ làm cho biến đó không thay đổi, không phải nội dung được chỉ định của nó (ví dụ trong trường hợp nội dung là một đối tượng, điều này có nghĩa là đối tượng chính nó vẫn có thể bị thay đổi).

//Cách viết trong ES6
const PI = 3.141593
PI > 3.0

//Cách viết trong ES2015
Object.defineProperty(typeof global === "object" ? global : window, "PI", {
  value: 3.141593,
  enumerable: true,
  writable: false,
  configurable: false
})
PI > 3.0;

Dễ dàng thấy là cách khai báo mới trong ES6 đã được tối ưu một cách đáng kể về số lượng dòng code.
2.2 Scoping

  • Block-Scoped Variables
//Cách viết trong ES6
for (let i = 0; i < a.length; i++) {
    let x = a[i]}
for (let i = 0; i < b.length; i++) {
    let y = b[i]}

let callbacks = []
for (let i = 0; i <= 2; i++) {
    callbacks[i] = function () { return i * 2 }
}
callbacks[0]() === 0
callbacks[1]() === 2
callbacks[2]() === 4

//Cách viết trong ES2015
var i, x, y;
for (i = 0; i < a.length; i++) {
    x = a[i];}
for (i = 0; i < b.length; i++) {
    y = b[i];}

var callbacks = [];
for (var i = 0; i <= 2; i++) {
    (function (i) {
        callbacks[i] = function() { return i * 2; };
    })(i);
}
callbacks[0]() === 0;
callbacks[1]() === 2;
callbacks[2]() === 4;

Bây giờ theo ES6 chúng ta không cần khai báo rồi mới được gọi trong điều kiện nữa mà có thể trực tiếp khai báo trong điều kiện và đặc biệt hơn cách khai báo let giúp biến được khởi tạo chỉ có phạm vi sử dụng xung quanh block bao quanh nó khác với var có phạm vi rộng khắp cả function đó.

  • Block-Scoped Functions
//Cách viết trong ES6
{
    function foo () { return 1 }
    foo() === 1
    {
        function foo () { return 2 }
        foo() === 2
    }
    foo() === 1
}

//Cách viết trong ES2015
(function () {
    var foo = function () { return 1; }
    foo() === 1;
    (function () {
        var foo = function () { return 2; }
        foo() === 2;
    })();
    foo() === 1;
})();

Tương tự đối với các function, chúng ta có thể khai báo trực tiếp và sử dụng mà không cần thông qua 2 bước nữa.
2.3 Arrow functions Cá nhân mình thấy phần này khá hữu ích trong ES6 vì nó rút gọn được một phần code cách khai báo cũng như cách gọi của functions mà trong các đoạn code sử dụng javascript sử dụng khá nhiều.
Arrows là cách khai báo hàm sử dụng cú pháp =>. Nó hỗ trợ cả cấu trúc thân hàm kiểu khối (block body) và thân hàm biểu thức (expression body). Và một điều khá "hay ho" đó là nó giúp xử lý trực quan hơn với đối tượng hiện tại (đó là việc sử dụng chung đối tượng this của function cha, điều mà các function trước đây cần phải khai báo hoặc sử dụng bind(this)).

//Cách viết trong ES6
this.nums.forEach((v) => {
    if (v % 5 === 0)
        this.fives.push(v)
})

//Cách viết trong ES2015
var self = this;
this.nums.forEach(function (v) {
    if (v % 5 === 0)
        self.fives.push(v);
});


2.4 Xử lý thông số mở rộng
2.4.1 Giá trị mặc định của tham số ES6 hiện tại đã hỗ trợ gán giá trị mặc định cho một tham số trong hàm, việc mà trước đây chúng ta cần check điều kiện rồi mới gán được giá trị mặc định.

function f (x, y = 7, z = 42) {
    return x + y + z
}
f(1) === 50


2.4.2 Tham số không giới hạn (Rest Parameter) Thêm một điểm mới của ES6 khá thú vị đó là việc nhận một tham số không giới hạn và tham số này được truy nhập như một mảng.

//Cách viết trong ES6
function f (x, y, ...a) {
    return (x + y) * a.length
}
f(1, 2, "hello", true, 7) === 9

//Cách viết trong ES2015
function f (x, y) {
    var a = Array.prototype.slice.call(arguments, 2);
    return (x + y) * a.length;
};
f(1, 2, "hello", true, 7) === 9;


2.5 Gán giá trị cho mục tiêu
2.5.1 Matching giá trị của mảng Thể hiện sự linh hoạt trực quan và sư linh hoạt khi biến các thành phần của mảng thành các biến riêng lẻ trong quá trình gán giá trị.
Cách viết trong ES6:

var list = [ 1, 2, 3 ]
var [ a, , b ] = list
[ b, a ] = [ a, b ]

Không khó khăn trong việc swap như trong cách viết của ES2015.

var list = [ 1, 2, 3 ];
var a = list[0], b = list[2];
var tmp = a; a = b; b = tmp;


2.5.2 Matching giá trị của một object Dù cấu trúc của object có nâng cáo hơn thì việc matching giá trị cũng trở nên dễ chịu hơn nhiều trong ES6. Cũng tương tự như mảng, sang ES6 cấu trúc trực quan và linh hoạt của các đối tượng được chuyển thành các biến riêng lẻ trong quá trình chuyển nhượng được sử dụng như một biện pháp khá hay.
Có thể dễ dàng thấy thông qua các dòng code được viết theo ES6 và ES2015.

//Cách viết trong ES6
var { op: a, lhs: { op: b }, rhs: c } = getASTNode();

//Cách viết trong ES2015
var tmp = getASTNode();
var a = tmp.op;
var b = tmp.lhs.op;
var c = tmp.rhs;


2.6 Module
2.6.1 Export/Import giá trị Hỗ trợ exporting/importing giá trị from/to các mô-đun mà không bị ô nhiễm không gian tên toàn cấu trúc của code.
Có lẽ thể hiện qua code các bạn sẽ thấy rõ nhất, trong ES6:

//  lib/math.js
export function sum (x, y) { return x + y }
export var pi = 3.141593

//  someApp.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))


Trong ES2015

//  lib/math.js
LibMath = {};
LibMath.sum = function (x, y) { return x + y };
LibMath.pi = 3.141593;

//  someApp.js
var math = LibMath;
console.log("2π = " + math.sum(math.pi, math.pi));

//  otherApp.js
var sum = LibMath.sum, pi = LibMath.pi;
console.log("2π = " + sum(pi, pi));

Thay vì việc trong thư viện phải khai báo rất nhiều và đến nơi cần sử dụng cũng phải khai báo biến đại diện thì ES6 đã hỗ trợ để import trực tiếp dưới một định danh để có thể sử dụng dễ dàng cũng như tránh trùng lặp với các hàm đã có sẵn trong file js mà ta đang code.
2.6.2 Mặc định và ký tự đại diện Đánh dấu một giá trị làm giá trị exported mặc định và khối lượng hỗn hợp các giá trị.
trong ES6

//  lib/mathplusplus.js
export * from "lib/math"
export var e = 2.71828182846
export default (x) => Math.exp(x)

//  someApp.js
import exp, { pi, e } from "lib/mathplusplus"
console.log("e^{π} = " + exp(pi))


trong ES2015

//  lib/mathplusplus.js
LibMathPP = {};
for (symbol in LibMath)
    if (LibMath.hasOwnProperty(symbol))
        LibMathPP[symbol] = LibMath[symbol];
LibMathPP.e = 2.71828182846;
LibMathPP.exp = function (x) { return Math.exp(x) };

//  someApp.js
var exp = LibMathPP.exp, pi = LibMathPP.pi, e = LibMathPP.e;
console.log("e^{π} = " + exp(pi));


Nhìn vào 2 đoạn code, điều đầu tiên có thể thấy là sự đơn giản trong việc export/import của ES6 cho người dùng một trải nghiệm khá thoải mái. Khác với sự khai báo "lằng nhằng" được sử dụng trong đoạn code thứ 2.
2.7 Class, một cấu trúc mà chúng ta thường xuyên sử dụng
2.7.1 Đầu tiên là khai báo Class

//Cách viết trong ES6
class Shape {
    constructor (id, x, y) {
        this.id = id
        this.move(x, y)
    }
    move (x, y) {
        this.x = x
        this.y = y
    }
}

//Cách viết trong ES2015
var Shape = function (id, x, y) {
    this.id = id;
    this.move(x, y);
};
Shape.prototype.move = function (x, y) {
    this.x = x;
    this.y = y;
};


Tổng quan là cách viết của ES6 đưa việc khai báo một Class vào một khối. Việc làm này giúp các developer nhìn thấy phạm vi xử lý của Class khá là tổng quan, thay vì trước đây mọi việc đều tách riêng thành các block khác nhau, đến khi tìm để xử lý từng thành phần của Class thì tương đối rối mắt.
2.7.2 Kế thừa trong Class Khá trực quan, theo phong cách hướng đối tượng và kế thừa từ bản mẫu.

class Rectangle extends Shape {
    constructor (id, x, y, width, height) {
        super(id, x, y)
        this.width  = width
        this.height = height
    }
}
class Circle extends Shape {
    constructor (id, x, y, radius) {
        super(id, x, y)
        this.radius = radius
    }
}

Rất dễ nhìn đúng không nào? Việc extends từ Class muốn kế thừa cho người dùng thấy luôn một các tổng quan là Class mình đang làm việc được kế thừa từ đâu mà không cần phải tìm lại dòng code khởi tạo Class để xem nó được kế thừa từ đâu giống như trước đây.

3. Tổng kết.

Bài viết trên đây mình xây dựng cho những bạn mới bắt đầu tìm hiểu về ES6 giống như mình.
ES6 đã thu gọn số lượng dòng code, làm trực quan rất nhiều vấn đề để các developer có thể xây dựng các source code một cách thoải mái nhất.
Thêm vào nữa, ES6 là một ngôn ngữ đặc tả đến thời điểm này là hoàn thiện nhất và còn rất nhiều các tính năng nữa nhưng do chính bản thân mình chưa tìm hiểu hết cũng như bài viết đầu tiên về ES6 của mình cũng còn nhiều thiếu sót để viết tiếp về các vấn đề khác. Mình xin dừng bài tìm hiểu ở đây và mong được các bạn đóng góp nhiều để bài viết trở nên hữu ích hơn nữa. Mong rằng sẽ sớm đưa ra được cho các bạn một bài nữa để nêu hết ra được những thay đổi của ES6.
Cảm ơn sự theo dõi của mọi người với bài viết này.