ES6

JS Jan 31, 2021

Chao xìn mọi người, mình là developer siêu cấp vip pro vĩ đại.
Hôm nay mình sẽ chia sẻ cho mọi người một ít kiến thức về ES6 nhóe.  😇😇
Lẹt gâuu. 😉

Đầu tiên nói qua về javascript một tý. Js là một ngôn ngữ rất thịnh hành trong giới lập trình, nhất là về mảng lập trình web. Tuy nhiên nó cũng là một ngôn ngữ rất dễ gây nhầm lẫn. Trước đây khi mới bước vào con đường lập trình, mình đã khá là đau đầu khi đọc code js với những khái niệm như con trỏ this, bind, apply, hàm callBack … vân vân và mây mây. Tuy nhiên cùng với sự phát triển như vũ bão của lập trình nói chung cũng như lập trình web nói riêng, cú pháp của js đã được thay đổi rất nhiều để trở nên dễ đọc, dễ học, và cải tiến những hạn chế của bản thân nó. Nổi bật là sự ra đời của ES6. I Love ES6 😘 I Love JS 😘.
Sau đây chúng ta cùng đi vào chi tiết những tính năng nổi bật của ES6 nhé

Default Parameters
Trước đây với Es5 để set default parameters cho hàm ta thường làm như sau

function infoStaff (name, age, address, salary) {
  return {
   name = name || 'NA',
   age = age || 0,
   address = address || 'NA',
   salary = salary || 500
  }
}

Cách này vừa dài dòng vừa tồn tại một số vấn đề, ví dụ thằng T trong công ty vừa code tù vừa bố láo nên tháng này không tính lương cho nó, khi bạn truyền salary là 0 vào hàm, trong javascript thì 0 được coi là false và thế là cuối tháng kế toán vẫn chuyển khoản cho thằng chả 500 đô ~~! cay không ? cay vch !!
Để bớt cay thì ta có thể dùng cú pháp của ES6 như sau

function infoStaff (name='NA', age=0, address='NA', salary=500) {
  return {
   name,
   age,
   address,
   salary
  }
}

xong !! sau một hồi cãi vã thì thằng T nộp đơn nghỉ việc và về nhà lên face bóc phốt 😅😅😅

Block-Scoped Constructs Let and Const
Trong javascript có một khái niệm là Hoisting, nó là một hành động mặc định của javascript, Trình biên dịch của Javascript sẽ chuyển phần khai báo của biến và hàm lên trên top, mình đã từng dính mấy bug do hoisting và debug khá vất vả

var foo = 1
function printFoo(check){
    if(check){
        var foo = 2;
    }
    return foo;
}
console.log(printFoo(false)) //undefined
console.log(printFoo(true)) //2

ES6 giới thiệu letconst như hai cách khai báo biến mới, hỗ trợ tầm vực theo khối (block scope) và không thực hiện hoisting.

let foo = 1
function printFoo(shouldDo) {
  if (shouldDo) {
    let foo = 2
    console.log('Value of foo in scope', foo) // 2
  }
  console.log('foo is out of block scope', foo) // 1
  return foo
}
console.log(printFoo(false)) // 1
console.log(printFoo(true)) // 1

Điểm khác biệt giữa let và const là với const, bạn không thể gán giá trị mới cho biến sau khi khai báo, trong khi điều này lại có thể với let.

let foo = 2
foo = 3
console.log(foo) // 3

const baz = 2
baz = 3 // Error: Assignment to constant variable.

const mang ý nghĩa "constant" chứ không phải "immutability". Nghĩa là với các biến là object hay array, bạn vẫn có thể thay đổi giá trị bên trong của chúng

const obj = { foo: 2 }
obj.foo = 5
obj.bar = 3
console.log(obj) // { foo: 5, bar: 3 }

const arr = [1]
arr.push(2)
console.log(arr) // [1, 2]

// Tuy vậy bạn không thể gán một đối tượng khác cho obj/arr
obj = { baz: 4 } // Error: Assignment to constant variable.
arr = [] // Error: Assignment to constant variable.

Trong ES6 thì bạn nên dùng Const cho tất cả khai báo biến vì nó sẽ giúp bạn hạn chế trường hợp "vô tình" thay đổi giá trị của biến. Chỉ dùng let  trong trường hợp bất khả kháng, và tránh xa thằng var ra nhé.

Template Literals và Multi-line Strings
Template Literal là cách chúng ta ghép giá trị của biến vào trong string, thay vì dùng dấu + để nối string và biến như trước đây

var url = 'http://localhost:3000/' + id

ta có thể viết như sau trong ES6

var url = `http://localhost:3000/${id}`

đặt tất cả trong cặp dấu ` (nằm dưới Esc trên bàn phím) và bọc các biến trong ${}, trông gọn gàng và dễ nhìn hơn hẳn đúng không nào 😎

ngoài ra Es6 còn hỗ trợ Multi-line Strings rất tiện,trước đây khi viết một chuỗi và muốn xuống dòng ta sử dụng syntax "\n\t"

var boy = 'boy đẹp trai,\n\t'
	 + 'tốt tính, \n\t'
	 + 'đa tài, \n\t'

Bây giờ trong Es6, đặt chuỗi trong cặp dấu `` và chúng ta cứ thế mà xuống dòng thôi 🤗

var boy = `boy đẹp trai,
	    tốt tính,
	    đa tài,`

Destructuring Assignment
ví dụ bạn có một object student

var student = {
    name: 'nam',
    class: '12c',
    sex: 'male'
}

Trong Es5 để lấy các thuộc tính trong object ra và gán vào biến để sử dụng ta làm như sau.

var name = student.name
var class = student.class
var sex = student.male

Với Es6 ta có thể viết ngắn gọn lại

const { name, class, sex } = student
console.log(name) //nam
console.log(class) //12c
console.log(sex) //male

với mảng hoặc chuỗi

const arr = [1, 2, 3]
const [first, second] = arr
console.log(first, second) // 1, 2

const str = 'Hello'
const [first, second] = str
console.log(first, second) // 'H', 'e'

Bạn có thể bỏ qua phần tử không mong muốn khi destructuring

const arr = [1, 2, 3]
const [first, , third] = arr
console.log(first, third) // 1, 3

const str = 'Hello'
const [fst, , , , lst] = str
console.log(fst, lst) // 'H', 'o'

Bạn cũng có thể destructuring các thuộc tính lồng nhau.

const userList = [
  {
    name: 'John',
    age: 21,
    products: [{ name: 'Creamy Crustacean Omelette', price: 1200 }, { name: 'Galdin Gratin', price: 2300 }]
  }
]

const [{ products: [{ price }] }] = userList
console.log(price) // 1200

Destructuring  cũng rất thường gặp khi bạn sử dụng ES6 module.

import { Component } from 'react'
import { render } from 'react-dom'

Enhanced Object Literals
ES6 mặc định gán giá trị cho property khi property có tên match với tên của biến,

function createStudent (name, age, sex) {
  return {
   name : name,
   age : age,
   sex : sex
   }
}

Sang Es6 chúng ta chỉ cần

function createStudent (name, age, sex) {
  return {
   name,
   age,
   sex
   }
}

Ngoài ra từ ES6 bạn cũng có thể khai báo thuộc tính cho object một cách linh động bằng cách sử dụng cú pháp [].

function createStudent (name, age, sex, teacher) {
  return {
    ['teacherOf' + name] : teacher,
  }
}

Modules
ES6 cho phép chúng ta importexport các object, dưới đây là những cách importexport

const funcB ()=> console.log('funcB')
const funcC ()=> console.log('funcC')

export default function FuncA(){
    return 'FuncA';
}
export {funcB, funC}

Import và sử dụng object/function được export từ file khác thì làm như sau

import funcA, {funcB, funcC} from 'app/helper';

funcA();
funcB();
funcC();

Arrow functions
Arrow functions -- là một kiểu cú pháp rút gọn cho khai báo hàm trong JavaScript. Trước ES6, bạn khai báo một hàm trong JavaScript với từ khóa function.

function add(x, y) {
  return x + y
}

// Hàm add() ở trên là syntactic sugar cho...
var add = function(x, y) {
  return x + y
}

Với hàm mũi tên trong ES6, bạn có thể viết lại thành

const add = (x, y) => {
  return x + y
}

// Bạn cũng có thể viết dưới dạng biểu thức (expression), hàm mũi
// tên sẽ tự động trả giá trị về (auto-return).
const add = (x, y) => x + y

Gọn gàng, dễ đọc, dễ hiểu hơn nhiều đúng không nào 😊
Ngoài ra như đã nói ở đầu bài viết, một vấn đề muôn thuở trong JavaScript, khái niệm con trỏ this. Với ES5, bạn hay gặp trường hợp giống như thế này

'use strict'
function App() {
  this.count = 0
  setInterval(function() {
    console.log(this.count++)
  }, 1000)
}

var a = new App()

Trước ES6, mỗi khai báo hàm đều có một giá trị this tách biệt. Điều này làm cho đoạn code ở trên không hoạt động, vì this.count bên trong hàm của setInterval mang giá trị undefined. Cách giải quyết thông thường là đặt một biến self, that hay _this để giữ reference, hoặc sử dụng Function.prototype.bind.

function App() {
  this.count = 0
  var self = this
  setInterval(function() {
    console.log(self.count++)
  }, 1000)
}

// hoặc
function App() {
  this.count = 0

  function counter() {
    console.log(this.count++)
  }

  setInterval(counter.bind(this), 1000)
}

Với hàm mũi tên trong ES6, giá trị của this chính là this trong tầm vực gần nhất với nó (lexical this). Do đó chúng ta không cần phải khai báo biến tạm hay dùng .bind nữa.

function App() {
  this.count = 0
  setInterval(() => console.log(this.count++), 1000)
}

Hàm mũi tên cũng rất hữu ích khi thao tác trên mảng và tiến hành chuyển đổi dữ liệu, giúp mã nguồn dễ đọc và rõ ràng hơn.
Rest and spread
đây là một tính năng mình rất thích ở Es6.
Bạn dùng dấu ... để biểu thị rest.

const [first, second, ...others] = [1, 2, 3, 4, 5]

console.log(first, second, others)
// 1
// 2
// [3, 4, 5]

Rest cũng được dùng khi khai báo hàm có thể nhận nhiều tham số

const foo = (...props) => console.log(props)
console.log(foo(1, 2, 3)) //[ 1, 2, 3 ]

Spread là thao tác ngược lại với rest, giúp bạn kết hợp một mảng đã có sẵn thành mảng mới.

const arr = [3, 4, 5]
const newArr = [1, 2, ...arr, 6]
console.log(newArr) // [1, 2, 3, 4, 5, 6]

Spread rất hữu ích để thay thế các thao tác trên mảng, như .concat() hay push

const head = [1, 2]
const tail = [3, 4, 5]
console.log([...head, ...tail]) // [1, 2, 3, 4, 5]

Rest/spread cũng có thể hoạt động trên object

const obj = { a:1,b:2,c:3 }

const newObj = {f:0, ...obj, d: 4}
console.log(newObj) // { f:0, a:1, b:2, c:3, d:4 }

// khi muốn thay đổi obj
const newObj = {...obj, c:9 }
console.log(newObj) // { a:1, b: 2, c: 9 }

Promise

Trong Es5 để xử lý bất đồng bộ, chúng ta dùng callback.
Callback hiểu đơn giản là bạn truyền một hàm B vào hàm A dưới dạng 1 tham số, một lúc nào đó thì hàm A sẽ gọi hàm B để chạy.

function asyncFunction(callback) {
   console.log("Start");
   setTimeout(() => {
      callback();
   }, 1000);
   console.log("Waiting");
}

let printEnd = function() {
   console.log("End");
}

asyncFunction(printEnd)

Nhược điểm của callback là nếu như bạn cần thực hiện nhiều câu lệnh bất đồng bộ thì bạn cần phải lồng từng đó callback với nhau, khiến cho code sẽ vô cùng khó đọc, khó debug cũng như phát triển (trường hợp này được gọi là Callback Hell),

function getData(link, callback) {
   setTimeout(() => {
      callback();
   }, 1000)
}

getData("Data1", () => {
   getData("Data2", () => {
      getData("Data2", () => {
         getData("Data3", () => {
            getData("Data4", () => {
               getData("Data5", () => {
                  getData("Data6", () => {
                     console.log("Done");                     
                  })
               })
            })
         })
      })
   })
})

Để giải quyết vấn đề Callback Hell ở trên, phiên bản ES6 đã đem đến cho chúng ta Promise. Về khái niệm, Promise chính là "lời hứa" đại diện cho giá trị chưa tồn tại và giá trị đó sẽ được trả về vào một thời gian trong tương lai.

//Đây là cách để tạo ra một Promise

let promise = new Promise((resolve, reject) => {
  // Asynchronous Code.
});

Promise sẽ nhận vào một hàm callback gồm 2 tham số như sau:
+ resolve: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise chạy thành công.
+ reject: một function sẽ được gọi nếu đoạn code bất đồng bộ trong Promise có lỗi xảy ra.

Promise cũng cung cấp cho chúng ta 2 phương thức để xử lý sau khi được thực hiện:
+then(): Dùng để xử lý sau khi Promise được thực hiện thành công (khi resolve được gọi).
+ catch(): Dùng để xử lý sau khi Promise có bất kỳ lỗi nào đó (khi reject được gọi).

Ngoài ra trong Es7 đã cho ra đời Async/await được xây dựng trên Promises

async function getJSONAsync() { 
    let json = await axios.get('https://tutorialzine.com/abc');
    return json;
}

Chỉ cần khai báo một hàm bất đồng bộ bằng từ khóa async, sau đó trong hàm này chỉ cần dùng await đặt phía trước Promise và nó sẽ chờ cho Promise chạy xong trả về kết quả mới chạy tiếp code phía sau. trong ví dụ trên, api gọi đi và được trả về xong xuôi, gán vào biến json thì hàm mới return.

Các bạn lưu ý khi xử lý bất đồng bộ trong vòng lặp thì dùng for of nhé, mình đã từng dính bug xử lý bất đồng bộ khi dùng các thể loại forEach, map ... và mất kha khá time để tìm hiểu tại sao code không chạy như mong muố n 😉.

Trên đây là một vài tính năng nổi bật của Es6, ngoài nổi bật ra Es6 còn một vài tính năng thú vị hay ho mà mình muốn giới thiệu, đã nốt công viết thì viết luôn cho bõ, các bạn đọc tiếp nhé

Format "currency"
ES6 cung cấp cho chúng ta phương thức Intl.NumberFormat dùng để định dạng tiền tệ

const money = 100000;

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
}).format(money); // '$ 100,000'

new Intl.NumberFormat('jp-JP', {
  style: 'currency',
  currency: 'JPY',
}).format(money); // 'JP¥ 100,000'

Remove Dulicates Array

const arrray = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];

const removeDuplicate = [...new Set(arrray)];

console.log(removeDuplicate);
// [1,2,3,4]

đơn giản phải không nào

Test 1 variable with NaN value
Javscript có một điều thật kỳ lạ, NaN là giá trị duy nhất KHÔNG BAO GIỜ bằng chính nó. Vậy làm thế nào để chúng ta kiểm tra tính đúng sai của nó?

const value = NaN;

value === NaN;
// false

Đừng lo, ES6 đã giới thiệu một phương pháp mới giải quyết vấn đề này là Object.is

const divide = 5 / "two"; //NaN

divide === NaN;

Object.is(divide, NaN); // true

Compute with string data type
Nếu giả sử bạn nhận được dữ liệu trả về bao gồm cả số và chuỗi, nhưng vì 1 lý do nào đó mà bạn...lười không muốn mất công convert string qua number rồi mới thực hiện việc tính toán, vậy thì áp dụng ngay "mẹo" dưới đây xem sao

const string = '100';
const number = 5;

// Nếu thực hiện tính tổng thông thường
console.log(string + number); // 1005

// Hack 1 chút với phương thức +string
console.log(+string + number); // 105

Rounds a decimal number to an integer
Để làm tròn chữ số thập phân thành số nguyên, có thể các bạn đều đã biết tới cách dùng hàm parseInt. Tuy nhiên, parseInt có 1 điểm trừ đó là...

const number = 1600000000000000000000.8;

const result = parseInt(number);

console.log(result); // 1

...oh no, no no no no no no, tại sao lại là 1, kết quả mong đợi của chúng ta phải là 1.6+e21.
Bởi vì khi số có giá trị quá lớn và được convert qua giá trị mới chứa chuỗi (ký tự ., +, e) thì hàm parseInt sẽ chỉ lấy ký tự đầu tiên mà thôi. Vì vậy, để hàm parseInt hoạt động chính xác, đầu tiên chúng ta cần convert kiểu dữ liệu number qua string rồi mới thực hiện làm tròn.

Và để giải quyết sự "rối rắm" trên, ES6 đã cung cấp cho chúng ta 1 hàm để làm việc này mà không cần phải quan tâm đến giá trị đầu vào của number là bao nhiêu.

const number = 1600000000000000000000.8;

const result = Math.trunc(number);

console.log(result); // "1.6e+21"

Test Array's data type
Trong JavaScript, Array thật sự không phải là...Array, bản chất của Array là Object. Vì vậy, bạn không thể chỉ đơn giản là dùng typeof để kiểm tra tính đúng đắn của kiểu dữ liệu Array

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

typeof array; // 'object'

Nhưng đừng lo lắng! ES6 cung cấp cho chúng ta 1 function đó là Array.isArray(), giúp dễ dàng hơn để kiểm tra xem một giá trị có phải là một Array hay không.

const array1 = [];

Array.isArray(array1); // true

const array2 = [1, 2, 3, 4];

Array.isArray(array2); // true

// Object
Array.isArray({}); // false

// Object
Array.isArray({ name: 'Sun' }); // false

// Number
Array.isArray(1000); // false

// Boolean
Array.isArray(true); // false

Bài đến đây cũng đã dài, hẹn gặp lại các bạn trong một bài viết khác, giới thiệu vài tính năng từ Es7 đến Es11 chẳng hạn 😘. paii paii

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.