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 ...
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, awidth, height) { super(id, x, y) this.awidth = awidth 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.