Xin chào các bạn, mình là một newbie mới bắt đầu tìm hiểu ngôn ngữ javascript, trước đây mình là dev HTML/CSS cho mảng template Joomla ở Bravebits. Hiểu được xu hướng và sự mạnh mẽ của javascript trong việc xây dựng một website, qua một thời gian ngắn tìm hiểu mình đã thực sự bị cuốn hút bởi nó và điều đó là động lực để mình xin chuyển qua team JS của công ty. Và may mắn hơn, khi công ty đã tạo điều kiện cho mình học tập và làm việc với JS.
Hôm nay mình sẽ cùng các bạn tìm hiểu có những thứ gì mới trong ES6 và những thay đổi so với phiên bản trước (ES5). Qua đó nhằm trả lời cho câu hỏi ở tiêu đề bài viết. Lan man hơi nhiều, bắt đầu thôi nào =))
1. Default Parameters
Default Parameter được hiểu là giá trị mặc định của tham số khi truyền vào các function. Đối với Javascript thì có nhiều bạn chưa biết chức năng này mặc dù trong ES5 đã cung cấp sẵn cho chúng ta, tuy nhiên người ta cảm thấy cách tạo giá trị mặc định trong ES5 vẫn không hay nên họ đã bổ sung một cách khác mới hơn và đơn giản hơn rất nhiều trong ES6.
Trước đây với ES5 để định nghĩa tham số mặc định chúng ta dùng cú pháp sau:
var item = function (height, color, fontSize) { var height = height || 50 var color = color || 'red' var fontSize = fontSize || '14px' }
Sẽ đơn giản và ngắn gọn hơn với ES6:
var item = function(height = 50, color = 'red', fontSize = '14px') { //something }
2. Rest Parameters
Rest Parameters được hiểu đơn giản là tham số còn lại, điều này có nghĩa là bạn có thể khai báo một hàm với số lượng tham số không xác định. Để khai báo các tham số còn lại của một function thì bạn đặt 3 dấu chấm . trước biến đại diện.
let functionName = (param1, param2, ...other) => { // }
Ví dụ: In ra tất cả giá trị của tham số truyền vào trong hàm.
// Khai báo hàm let domainList = (main, sub, ...other) => { console.log("Main: " + main); console.log("Sub: " + sub); console.log("Other"); console.log(other); } // Gọi hàm domainList('joomlashine.com', 'facebook.com', 'google.com', 'zalo.com', 'iphone.com');
Hàm này có 3 tham số truyền vào là main, sub và Rest Parameter other. Chạy lên bạn sẽ thấy kết quả như sau:
Trường hợp tham số truyền vào vừa đủ thì Rest Parameter sẽ có giá trị là một mảng rỗng. Xem ví dụ:
let domainList = (main, sub, ...other) => { console.log("Main: " + main); console.log("Sub: " + sub); console.log("Other"); console.log(other); } // Gọi hàm domainList('joomlashine.com', 'facebook.com');
Kết quả:
3. Arrow Function
Arrow Function là một cách định nghĩa function mới bằng cách sử dụng dấu mũi tên =>. Cách này được đưa vào bộ ES6 với cú pháp mới lạ và ngắn gọn hơn.
Trong Javascript để tạo một function thì thông thường chúng ta sử dụng hai cách sau:
// Cách 1 function Name(var1, var2){ } // Cách 2 var Name = function(var1, var2,){ }
Cú pháp căn bản nhất của arrow function như sau:
var functionName = (var1, var2) => { // Nội dung function };
Ví dụ:
var hello = (name, message) => { console.log("hello " + name + ", welcome to " + message); }; hello('dungpv', 'javascript');
Với trường hợp có 1 tham số thì chúng ta có thế bỏ cặp dấu ()
var hello = message => { console.log(message); }; hello('hello world');
Trường hợp không có tham số truyền vào chúng ta sử dụng cặp dấu () rỗng
var hello = () => { console.log('Hello World'); }; hello();
Khắc phục nhược điểm với this trong closure function
Như các bạn đã biết thì từ phiên bản ES5 trở về trước, sẽ có nhược điểm với đối tượng this đó là phạm vi hoạt động. Trong ES5 chúng ta thường sử dụng hàm bind để khắc phục, và điều này được khắc phục hoàn toàn trong ES6 bằng cách sử dụng Arrow Function.
Xét ví dụ sau với ES5
var person = { name : "dungpv", showName : function (callbackFunction){ callbackFunction(); }, render : function(){ this.showName(function (){ console.log(this.name); // this chính là person }.bind(this)); // phải sử dụng hàm bind thì đối tượng this mới chính là person } }; person.render();
Với ES6 ta viết như sau
var person = { name : "dungpv", showName : function (callbackFunction){ callbackFunction(); }, render : function(){ this.showName((() => { console.log(this.name); // this chính là person })); } }; person.render();
4. HTML Template String
Template String là cách hiển thị của biến trong chuỗi với ES5 ta có cú pháp sau
var name = 'Your name is ' + first + ' ' + last + '.'
Template String sẽ thay thế cách nối chuỗi thông thường. Trong ES6 template string được bao bọc bởi cặp dấu ``
. Để khai báo tham số trong chuỗi Template String thì bạn sử dụng cú pháp ${ten_bien} và đặt vào vị trí muốn hiển thị.
var website = 'joomlashine.com'; let temp = ` Welcome to ${website} `; console.log(temp);
5. Let and Const
Trong ES6 cung cấp thêm 1 từ khoá nữa dùng để khởi tạo một biến đó là let, từ khóa này khác với từ khoá var ở chỗ phạm vi hoạt động. Với từ khóa var nếu ban khai báo biến bên trong hàm thì đó là biến cục bộ, còn nếu bạn khai báo bên ngoài hàm thì nó sẽ là một biến toàn cục. Còn với từ khóa let thì phạm vi hoạt động của nó nhỏ hơn, nó chỉ tồn tại bên trong khối đang khai báo và ta gọi đây là phạm vi block scoped.
if(true){ let name = "dungpv" }
Ở trên mình khai báo biến name, biến này chỉ hoạt động trong phạm vi khối lệnh if().
Với phạm vi hoạt động hẹp như vậy chúng ta thường dùng let để khai báo các biến có tính chất tạm thời, nghĩa là nó chỉ sống trong một phạm vi hoạt động của khối đó thôi, không sử dụng qua vị trí khác. Xét ví dụ dưới đây để hiểu rõ hơn về let
Bài toán: viết chương trình hoán đổi giá trị của 2 biến a và b, nếu giá trị của a < b
var a = 12; var b = 20; if (a < b) { var tmp = a; a = b; b = tmp; } console.log("a: " + a); console.log("b: " + b); console.log("tmp: " + tmp);
KQ:
Như vậy biến tmp sau khi kết thúc lệnh if nó vẫn tồn tại => dư thừa không cần thiết.
Sử dụng let cho bài toán này như sau:
var a = 12; var b = 20; if (a < b) { let tmp = a; a = b; b = tmp; } console.log("a: " + a); console.log("b: " + b); console.log("tmp: " + tmp); // đoạn này sẽ lỗi
KQ:
Đoạn code cuối cùng bị lỗi vì biến tmp được khai báo trong if(), và chỉ có phạm vi hoạt động trong khối lệnh đó.
Qua ví dụ trên chúng ta cũng đã nhận thấy được ưu và nhược điểm của let, với những bài toán cụ thể mà chúng ta có thể sử dụng let or var cho hợp lí.
Const trong ES6
Const là gì? const có nghĩa là hằng số nó đã tồn tại trong các ngôn ngữ lập trình cấp cao như C#, Java. Nhưng cho đến khi ra đời phiên bản ES6 Javascript mới có khái niệm này. Const là một hằng số, nên điều này có nghĩa là nếu một biến được khai báo là hằng số thì bạn phải gán giá trị lúc khai báo luôn, và kể từ đó về sau bạn sẽ không thể thay đổi giá trị cho biến đó được nữa. Tuy nhiên có một lưu ý là biến const là một block-scoped (giống với let), vì vậy nó chỉ tồn tại trong phạm vi nó được khai báo mà thôi.
Ví dụ giá trị của const là một object
const info = { name : "dungpv", age : 25 }; console.log(info);
6. Exporting & Importing Modules
Cú pháp Export và Import đã được thay đổi hoàn toàn trong phiên bản ES6, trước đây với ES5 để export và import ta dùng cú pháp sau:
var myModule = { x: 1, y: function(){ console.log('This is ES5') } } module.exports = myModule;
var myModule = require('./myModule');
Và đây là của ES6
const myModule = { x: 1, y: () => { console.log('This is ES6') } } export default myModule;
import myModule from './myModule';
Ngoài ra ES6 còn hỗ trợ cho chúng ta khả năng import và export nhiều module con hoặc các biến từ một module duy nhất.
Ví dụ trong file module bạn export một số thứ như sau
export const x = 1; export const y = 2; export const z = 'String';
Và bạn import như thế này
import {x, y, z} from './myModule';
7. Class in ES6
Trong javascript chúng ta có thể khai báo một đối tượng bằng cách, khai báo một biến có kiểu dữ liệu.
var employee = { name: null, age: null, setName: function (name) { this.name = name; }, getName: function () { return this.name; }, setAge: function (age) { this.age = age; }, getAge: function () { return this.age; } };
Nhưng cách khai báo đó nó đã quá cũ và không có chuẩn về cú pháp, điều đó khiến những người mới lập trình rất khó tiếp cận với javascript. Do đó trong phiên bản ES6 đã cải tiến rất nhiều về class với cú pháp chuẩn OOP.
Đối với VD trên chúng ta có thể chuyển sang dạng ES6 thành như sau:
class Employee { setName (name) { this.name = name; } getName () { return this.name; } setAge (age) { this.age = age; } getAge () { return this.age; } };
Như các bạn đã thấy thì về mặt cú pháp của nó rõ ràng hơn hẳn đúng không? Với ES6 thì bạn không thể khai báo các thuộc tính như bình thường được mà bạn chỉ có thể gán nó vào các phương thức trong đối tượng được thôi.
Để khởi tạo đối tượng được khai báo theo chuẩn ES6 thì các bạn sử dụng từ khóa new với cú pháp như sau:
new Employee();
Và với ES6, nó cũng hỗ trợ chúng ta một phương thức đặc biệt mà bất kỳ ngôn ngữ lập trình nào cũng có đối với class đó là constructor – phương thức khởi tạo. constructor trong ES6 cũng có tác dụng tương tự, nó sẽ tự động được gọi khi đối tượng được khởi tạo.
Để khai báo constructor trong ES6 thì các bạn chỉ cần khai báo một phương thức có tên là constructor.
class Employee { constructor (name, age) { this.name = name; this.age = age; } setName (name) { this.name = name; } getName () { return this.name; } setAge (age) { this.age = age; } getAge () { return this.age; } };
Như mình cũng đã nói ở trên thì với kiểu khai báo class trong ES6 chúng ta không thể khai báo trực tiếp thuộc tính cho nó được mà phải khởi tạo qua các phương thức và thường thì chúng ta sẽ đặt nó ở trong constructor luôn.
Lúc này khi khởi tạo đối tượng chúng ta có thể truyền luôn tham số cho nó như các ngôn ngữ khác.
new Employee("dungpv", 25);
Ngoài ra với ES6, nó cũng đã cung cấp cho chúng ta sử dụng từ khóa Extends để kế thừa từ đối tượng khác với cú pháp
class A extends B { //code }
8. Promise in ES6
Promise được đưa vào Javascript từ ES6, đây có thể coi là một kỹ thuật nâng cao giúp xử lý vấn đề bất đồng bộ hiệu quả hơn. Vậy promise sinh ra để xử lý kết quả của một hành động cụ thể, kết quả của mỗi hành động sẽ là thành công hoặc thất bại và Promise sẽ giúp chúng ta giải quyết câu hỏi “Nếu thành công thì làm gì? Nếu thất bại thì làm gì?“. Cả hai câu hỏi này ta gọi là một hành động gọi lại (callback action).
Khi một Promise được khởi tạo thì nó có một trong ba trạng thái sau:
- Fulfilled Hành động xử lý xong và thành công
- Rejected Hành động xử lý xong và thất bại
- Pending Hành động đang chờ xử lý hoặc bị từ chối
Trong đó hai trạng thái Reject và Fulfilled ta gọi là Settled, tức là đã xử lý xong.
Để tạo một Promise bạn sử dụng cú pháp sau:
var promise = new Promise(function(resolve, reject){ //code });
Trong đó:
- resolve là một hàm callback xử lý cho hành động thành công.
- reject là một hàm callback xử lý cho hành động thất bại.
Thenable trong Promise
Thenable là một phương thức ghi nhận kết quả của trạng thái (thành công hoặc thất baị) mà ta khai báo ở Reject và Resolve. Nó có hai tham số truyền vào là 2 callback function. Tham số thứ nhất xử lý cho Resolve và tham số thứ 2 xử lý cho Reject.
var promise = new Promise(function(resolve, reject){ resolve('Success'); // OR reject('Error'); }); promise.then( function(success){ // Success }, function(error){ // Error } );
Vậy hai hàm callback trong then chỉ xảy ra một trong hai mà thôi, điều này tương ứng ở Promise sẽ khai báo một là Resolve và hai là Reject, nếu khai báo cả hai thì nó chỉ có tác dụng với khai báo đầu tiên.
Vậy Promise là một gói dùng để quản lý kết quả trả về của một hành động Asycn (bất đồng bộ) và nó vừa được bổ sung vào ngôn ngữ Javascript từ version ES6. Việc nắm vững Promise không hề đơn giản và không phải ai cũng hiểu rõ, mình sẽ dành thời gian tìm hiểu thêm và sẽ có 1 bài viết riêng cho thằng này.
9. Set, Maps, WeakMap, WeakSet
Trong ES5 không tồn tại dữ liệu dạng cấu trúc tập hợp, vì vậy chúng ta sử dụng mảng để lưu trữ dữ tập hợp các phần tử. Tuy nhiên với ES6 thì mọi chuyện đơn giản hơn bởi vì nó có hỗ trợ kiểu dữ liệu tập hợp Set với các giá trị truyền vào tùy ý kèm theo tốc độ xử lý nhanh chóng.
Set trong ES6
Chúng ta có bốn thao tác chính khi làm việc với set
như sau:
- Khởi tạo:
let set = new Set();
- Thêm phần tử:
set.add(value);
- Xóa phần tử:
set.delete(value);
- Kiểm tra tồn tại giá trị:
set.has(value);
- Đếm tổng số phần tử:
set.size;
- Xóa toàn bộ phần tử:
set.clear();
Đối với Set thì các giá trị không được trùng, vì vậy nếu bạn cố tình thêm vào hai giá trị giống nhau thì nó chỉ lưu một lần mà thôi.
Lúc khởi tạo sẽ có một tham số truyền vào, tham số này bắt buộc phải là một mảng.
//Thêm phần tử var numbers = new Set([1, 2, 3, 4]); numbers.add(5); // numbers = Set {1, 2, 3, 4 ,5} //Xoá phần tử var numbers = new Set([1, 2, 3, 4]); numbers.delete(2); // numbers = Set {1, 3, 4} //Kiểm tra phần tử tồn tại var numbers = new Set([1, 2, 3, 4]); console.log(numbers.has(1)); // True console.log(numbers.has(5)); // False //Đếm số phần tử var numbers = new Set([1, 2, 3, 4]); console.log(numbers.size); // 4
Set là một tập hợp nên bạn hoàn toàn có thể lặp qua các phần tử như ví dụ sau:
let numbers = new Set([1, 2, 3, 4]); for (let number of numbers){ console.log(number); } // Output: // 1 // 2 // 3 // 4
Để chuyển đổi Set sang Array thì bạn sử dụng ba dấu chấm đặt trước biến Set.
let numbers = new Set([1, 2, 3, 4]); let arr_numbers = [...numbers]; // Lúc này biến arr_numbers sẽ là một mảng gồm 4 phần tử // [1, 2, 3, 4]
Ngược lại để chuyển đổi một Array sang Set thì bạn sử dụng cách sau:
let arr_numbers = [1, 2, 3, 4]; let set_numbers = new Set(arr_numbers); // Lúc này biến set_numbers sẽ là Set {1, 2, 3, 4}
Mapping và Filtering
Mapping là một hàm được tích hợp sẵn trong Array với chức năng là thiết lập giá trị cho phần tử trong môi lần lặp.
let set = new Set([1, 2, 3]); let arr = [...set].map(function(x){ return x * 2; }); console.log(set); // Set(1, 2, 3) console.log(arr); // [2, 4, 6]
Như vậy trong hàm mapping trả về giá trị bao nhiêu thì phần tử tại lần lặp đó sẽ có giá trị bấy nhiêu.
Filtering cũng được tích hợp sẵn trong Array, chức năng của hàm này là trả về true thì phần tử được chọn, trả về false thì phần tử không được chọn
let set = new Set([1, 2, 3]); // Lấy các số chẵn let arr = [...set].filter(function(x){ return (x % 2) == 0; }); console.log(set); // Set(1, 2, 3) console.log(arr); // [2]
Map trong ES6
Map là một kiểu dữ liệu tương tự như Set, tuy nhiên với Map thì có cấu trúc dạng key => value
còn với Set thì chỉ có value
.
Chúng ta có các thao tác chính với map như sau:
- Khởi tạo:
let map = new Map()
- Thêm phần tử:
map.set('Name', 'dungpv')
; - Xóa phần tử:
map.delete("Name");
- Kiểm tra phần tử tồn tại:
map.has('Name')
- Đếm tổng số phần tử:
map.size
- Xóa toàn bộ phần tử:
map.clear();
Đối với Map thì các key không được trùng, vì vậy nếu bạn truyền vào 2 key giống nhau thì nó chỉ lưu đè vào một key duy nhất.
//Thêm phần tử let map = new Map(); map.set('Name', 'dungpv'); console.log(map); // Map {"Name" => "dungpv"} //Xoá phần tử let map = new Map(); map.set('Name', 'dungpv'); console.log(map); // Map {"Name" => "dungpv"} map.delete("Name"); console.log(map); // Map {} //Kiểm tra phần tử tồn tại let map = new Map(); map.set('Name', 'dungpv'); console.log(map.has('domain')); // False //console.log(map.has('Name')); // True //Đếm tổng số phần tử let map = new Map(); map.set('Name', 'dungpv'); console.log(map.size); // 1 //Xoá toàn bộ phần tử let map = new Map(); map.set('Name', 'dungpv'); map.set('Domain', 'joomlashine.com'); map.clear(); console.log(map.size); // 0
WeakMap trong ES6
WeakMap là một loại kiểu dữ liệu giống như Map vậy, nghĩa là sẽ tồn tại hai tham số key => value
cho mỗi phần tử. Tuy nhiên với WeakMap thì key truyền vào phải là một biến và biến này phải là một Object (class, function, object), con với Map thì bạn có thể thiết lập key là chuỗi, number, object đều được.
// Khởi tạo var weak = new WeakMap(); // Danh sách key var key1 = {}; var key2 = {}; // Thêm phần tử weak.set(key1, "Giá trị 01"); weak.set(key2, "Giá trị 02"); // Lấy giá trị console.log(weak.get(key1)); // Giá trị 01 console.log(weak.get(key2)); // Giá trị 02 // Kiểm tra tồn tại var other_key = {}; console.log(weak.has(key1)); // true console.log(weak.has(other_key)); // false // Xóa phần tử weak.delete(key1); console.log(weak.get(key1)); // Undefined
WeakSet trong ES6
WeakSet có thể được coi là một phiên bản tương tự như Set, tuy nhiên với WeakSet
thì dữ liệu truyền vào luôn phải là một đối tượng (object, class, function) và bạn phải tạo một giá trị (key) trước khi lưu vào, điều này khác hoàn toàn với Set
là Set
có thể lưu trữ mọi dữ liệu kể cả number và string.
// Khởi tạo var weak = new WeakSet(); // Danh sách key var key1 = { name : "dungpv" }; var key2 = { address: "hatinh" }; // Thêm phần tử weak.add(key1); weak.add(key2); // Kiểm tra tồn tại var other_key = {}; console.log(weak.has(key1)); // true console.log(weak.has(other_key)); // false // Xóa phần tử weak.delete(key1);
10. Summary
Như vậy mình đã giới thiệu qua cho các bạn biết một số tính năng mới của ES6 (ECMAScript 6) và một vài so sánh tính năng so với phiên bản cũ (ES5). Rõ ràng với sự ra đời của ES6 đã mang lại nhiều điều mới mẻ và tiện dụng hơn trong Javascript. Hiện tại ES6 đã hoàn thành, nhưng chưa được hỗ trợ đầy đủ bởi các trình duyệt. Để sử dụng ES6, bạn cần một trình biên dịch như Babel (mình sẽ có 1 bài viết về thằng này). Bài viết này cũng khá dài rồi nên mình dừng ở đây, hẹn gặp lại các bạn ở bài viết sau. =))
Nguồn:
https://freetuts.net/hoc-ecmascript/ecmascript-2015-es6
https://codeburst.io/es5-vs-es6-with-example-code-9901fa0136fc