ES6 - The Good Part (Phần 1)

ES6 hay ECMAScript 2015, ES2015 là tập hợp các kỹ thuật nhằm code javascript clean and clear hơn. Nghe khái niệm này có vẻ hơi khó hiểu, tại sao 1 ngôn ngữ lại cần 1 cái chuẩn để viết được code (??). Mình sẽ giải thích qua một chút về vấn đề này trước khi đi sâu hơn vào ES6.

Trước tiên, tản mạn 1 chút về javascript và ECMAScript. Javascript ra đời năm 1995, tức là đã hơn 20 năm phát triển và hiện giờ đang là 1 trong các hot language trong giới lập trình viên. Tuy vậy đây cũng là một trong số các ngôn ngữ khó học và khó hiểu bậc nhất. Mình có thể khẳng định điều này vì mình đã học nó chục lần rồi =)) . Javascript gần như là 1 chú ngựa đơn độc trong rất nhiều các ngôn ngữ lập trình, ẩn trong nó là hàng tá các định nghĩa khó hiểu không kém: prototype, callback function, scope, hoisting, dynamic object, invocation pattern, ... Tuy vậy, với nhu cầu sử dụng ngày càng nhiều và dần trở nên quan trọng hơn đối với các website, việc code những hệ thống lớn, gồm hàng trăm module, dùng từ front end đến back end, từ web đến mobile app, desktop app mà chỉ có mỗi javascript thì gần như là quá tải, và sớm muộn gì mấy ông maintain cũng sẽ đến đốt nhà bạn. Giải pháp cho vấn đề trên nằm ngay trong bài viết này: ES6 nhằm hỗ trợ cho việc code trở nên dễ dàng và tường minh hơn. Nhưng ES6 không phải gồm 1 hay 2 technique mà gồm hàng tá kỹ thuật hỗ trợ, trong bài viết này mình sẽ chỉ đề cập đến những phần nào mình thấy hữu ích nhất qua quá trình tìm hiểu và sử dụng trong các project. Đồng thời cũng chỉ ra luôn điểm lợi, điểm hại, tips and trick khi sử dụng ES6.

Bài viết sẽ bao gồm các nội dung sau:

  • const vs let vs var
  • import/export
  • arrow function
  • template string
  • destructuring
  • for of vs for in
  • Array methods
  • spread operator
  • class

const vs let vs var

var ở trong javascript thì có lẽ ai cũng biết. var đứng đằng trước nhằm khai báo cho variable, tương tự với constlet. Nếu giống nhau thế thì tại sao ES6 lại sinh ra thêm 2 thanh niên kia để làm gì. Có 2 điều cần lưu ý ở đây, thứ nhất, var là function scope, còn constlet là block scope. function scope tức là miễn bạn khai báo biến ở trong một function, bạn có thể dùng nó ở bất cứ đâu trong function đó.

function run() {
    var m = 100;
    echo "you ran " + m + "meters";
    if (m > 20) {
        console.log(m,  "meters !!");
    }
}
run(); //you ran 100 meters;
          //100meters !! 
console.log(m); //undefined

Nhưng ở đây sẽ nảy sinh một vấn đề, hãy nhìn vào đoạn code dưới đây:

function run() {
    if (100 > 20) {
        var m = 100;
        console.log(m, "meters !!");
    }
    console.log("you ran ", m, "meters");
}
run();  //100meters !! 
            //you ran 100 meters;

Tôi đã đảo thứ tự khai báo variable m đi một chút, nhưng function run() vẫn hoạt động êm ru, sao lại thế? Bởi var là function scope, nên dù khai báo bên trong mệnh đề if nó vẫn được vô tư sử dụng bên ngoài, miễn là còn nằm trong function (không thể hư cấu hơn 😄 ). Giải pháp ở đây chính là const và let (block scope). Tương tự với function scope, miễn là variable khai báo với const hoặc let, ta sẽ dùng được nó tại bất cứ đâu bên trong { } (bên trong dấu đóng mở ngoặc).

function run() {
    if (100 > 20) {
        let m = 100;
        console.log(m, "meters !!");
    }
    console.log("you ran ", m, "meters");
}
run();  //100meters !! 
            //Reference error: m is not defined

Lưu ý thứ 2: bạn có thể khai báo cùng 1 biến với từ khóa var bao nhiêu lần bạn thích. Nhưng với constlet, sẽ chỉ có 1 lần khai báo thôi, nếu không sẽ ăn lỗi ngay.

var m = 10;
var m = 20; //ok
let n = 10;
let n = 20; //báo lỗi n đã được defined trước đó
// tương tự với const

2 lưu ý cũng chính là lợi ích trên đã giúp bạn quyết định dùng const, let thay cho var hay chưa. Nếu rồi, thì chắc hẳn bạn sẽ thắc mắc rốt cuộc const khác gì với let. constlet chỉ có một khác biết duy nhất, đó là các biến được khai báo với let có thể thay đổi được, còn với const thì không.

let a = 1;
a = 100; // ok
const b = 1;
b = 100 //error

Nhưng lưu ý với const là một object. Ta hoàn toàn có thể thay đổi value của các property bên trong nó.

const pets = {
    dog: 'ursa',
    cat: 'ladygaga'
}
pets.dog = 'ron'; // ok
pets = 100; // error
pets = {1, 2, 3}; //error

import/export

ES6 có sự thay đổi về cú pháp import/export so với javascript và es5. Nếu ai từng viết nodejs, chắc hẳn sẽ nhớ những dòng code này:

var handle = require('express')();
var app = require('http').createServer(handle);

đây chính là cách để import một module nào đó trong nodejs, cũng tương tự với javascript, nhưng điều này sẽ thay đổi với es6. Giả sử ta có 1 file config.js:

const people = [
    {
        name: 'James Nguyen',
        age: 18,
        gender: female
    },
    {
        name: 'Knox Overstreet',
        age: 28,
        gender: male
    },
];

export function findPerson(name) {
    for (let i = 0; i < people.length; i++) {
        if (people[i].name == name) {
            return people[i];
        }
    }
}

const anything = [1, 2, 3, 4, 5];
export default people;

Tại một file js khác app.js ta chỉ muốn import people, và function findPerson()

import people, { findPerson } from './config';
....

Một vài lưu ý:

  • Với những module không nằm trong node_modules folder, tức là module mà ta tạo ra, cần dùng relative path ./config chứ không phải config
  • module people được export default nên khi import, ta có thể dùng bất cứ tên nào mà ta muốn: import renamedPeople from './config';
  • với những module còn lại được import, cần gọi đúng tên module, và cú pháp hơi khác do với default module.

Arrow function

Arrow function là một annonymous function, hỗ trợ cho việc viết function một cách nhanh chóng hơn, ngoài ra không có tác dụng gì đáng kể và thậm chí sẽ gây phiền hà nếu lạm dụng nó. Hãy cùng xem một vài ví dụ về arrow function:

const numbers = [1, 3, 20, 40, 45, 70];
// sử dụng function thông thường
const result1 = numbers.filter(function(value, index) {
    return value > 50;
});

// arrow function
const result2 = numbers.filter((value, index) => {
    return value > 50
});

const result3 = numbers.filter(value => {
    return value > 50
});

const result4 = numbers.filter(value => value > 50);

const result5 = numbers.map(() => 'everything');

Có thể thấy là từ 3 dòng code, giờ ta chỉ còn lại đúng 1 dòng. Nhưng như tôi đã nói ở trên, việc lạm dụng arrow function sẽ đôi khi sẽ gây rắc rối cho chính bạn. Hãy nhìn vào ví dụ sau đây:

    const person = {
        age: 20,
        increaseAge: () => {
            this.age++;
        }
    }
    
    person.increaseAge();
    person.increaseAge();
    person.increaseAge();
    
    console.log(person.age); //20

person.age vẫn ra kết quả là 20 ?? tại sao lại thế. Hãy cùng log this ra xem sao:

     const person = {
        age: 20,
        increaseAge: () => {
            console.log(this);
            this.age++;
        }
    }
    
    person.increaseAge(); // window

Khi console.log(this) ta sẽ thu được Window. Tại sao lại là Window mà không phải là person? Đây chính là vấn đề của arrow function. Khi ta sử dụng arrow function, this sẽ không được bind cho person, mà là một object mà person kế thừa (chính là object Window) (đa phần trong các browser hiện nay sẽ là Window chứ không phải tất cả các bạn nhé). Khi này, hãy sử dụng function thông thường, vấn đề sẽ được giải quyết:

     const person = {
        age: 20,
        increaseAge: function() {
            console.log(this);
            this.age++;
        }
    }
    
    person.increaseAge(); // person
    console.log(person.age); //21

Template string

Template string bao gồm 1 số technique để xử lý string trong javascript.

var name= 'james'
// xử lý string thông th
var str = 'This is my name ' + name;
// template string
const str1 = `This is my name ${name}`;

Chèn thẻ html vào template string:

const name= 'james';
const btn = `
        <form>
            <input type='text' name='name' value=${name} />
            <button type='submit'>Submit<button>
        </form>
    `;
    
document.body.innerHTML = btn;

Thêm các xử lý cần thiết bên trong template string:

const friends= ['Harry Potter', 'Ron Weasley', 'Hermione Granger';
function renderFriends(friends) {
    return friends.map(value => <li>value</li>).join('');
}

const btn = `
        <h1>Friend List</h1
        <ul>${renderFriends(friends)}</ul>
    `;
    
document.body.innerHTML = btn;

Tagged template: thêm các tiền xử lý cần thiết với mỗi string:

const person = {name: 'james', age: 18};
function highlight(strings, ...variables) {
    let str = '';
    for (index in strings) {
        str += `${strings[index]}<b>${variables[index] || ''}</b>`;
    }
    
    return str;
}

const introduction =  highlight `Hi! I'm ${person.name}, ${person.age} years old. Nice to meet you`;
document.body.innerHTML = introduction;
console.log(introduction); //Hi! I'm <b>james</b>, <b>18</b> years old. Nice to meet you<b></b>

Ở đây, function highlight sẽ nhận string Hi! I'm ${person.name}, ${person.age} years old. Nice to meet you, phân tách thành các strings con dựa trên mỗi variable truyền vào. Hãy cùng log stringsvariable ra để xem các params này chứa gì: variables sẽ chứa tất cả các biến truyền vào, còn strings sẽ chứa các string còn lại, phân tách bởi các biến truyền vào. Thực chất việc sử dụng tagged template có phần rắc rối, nếu string cần sử lý đơn giản thì tôi khuyên bạn nên truyền trực tiếp thẻ HTML vào template string.

Ban đầu định đi hết tất cả nội dung, nhưng mình thấy hơi dài nên sẽ chia ra làm 2 phần, ở phần 1 ta đã đi qua const vs let vs var, import/export, arrow function, template string. Phần 2 sẽ đến với các nội dung còn lại trong menu trên. Cám ơn vì đã đọc tới tận dòng này của bài viết.


All Rights Reserved