Bài 6.3: Template literals và modules trong JS

Chào mừng bạn đến với một bài học thú vị khác trong hành trình chinh phục Lập trình Web Front-end cùng FullhouseDev! Hôm nay, chúng ta sẽ cùng đào sâu vào hai tính năng cực kỳ hữu íchhiện đại của JavaScript (từ phiên bản ES6 trở lên): Template Literals giúp chúng ta làm việc với chuỗi một cách thanh lịch hơn bao giờ hết, và Modulescứu cánh cho bài toán tổ chức code JavaScript quy mô lớn, tránh xung đột và dễ dàng quản lý các phần phụ thuộc.

Hãy cùng bắt đầu nhé!

1. Template Literals: Làm việc với chuỗi chưa bao giờ dễ dàng đến thế!

Trước khi ES6 ra đời, việc kết hợp biến và chuỗi trong JavaScript thường khá lộn xộn và khó đọc, đặc biệt với các chuỗi dài hoặc có nhiều biến. Chúng ta phải dùng phép nối chuỗi + liên tục.

Hãy xem ví dụ cũ:

let ten = "Nguyễn Văn A";
let tuoi = 30;

// Cách truyền thống (pre-ES6)
let thongBaoCu = "Chào bạn, tôi là " + ten + " và tôi " + tuoi + " tuổi.";
console.log(thongBaoCu);

Nhìn khá rắc rối phải không? Đặc biệt nếu bạn có nhiều biến hơn hoặc cần thêm các ký tự đặc biệt như dấu nháy kép, bạn sẽ phải làm thêm các bước escape ký tự (\").

Chào mừng đến với Template Literals!

Template Literals cho phép bạn định nghĩa chuỗi bằng cặp dấu backticks (``) thay vì dấu nháy đơn ('') hoặc nháy kép (""). Điều kỳ diệu xảy ra bên trong cặp backticks này:

1.1. Nội suy biểu thức (Expression Interpolation)

Bạn có thể nhúng trực tiếp các biến, biểu thức hoặc thậm chí là các lời gọi hàm vào trong chuỗi bằng cú pháp ${ }. JavaScript sẽ tự động đánh giá biểu thức bên trong ${} và chuyển đổi kết quả thành chuỗi để chèn vào vị trí đó.

Hãy xem lại ví dụ trên với Template Literals:

let ten = "Nguyễn Văn A";
let tuoi = 30;

// Sử dụng Template Literals (ES6+)
let thongBaoMoi = `Chào bạn, tôi là ${ten} và tôi ${tuoi} tuổi.`;
console.log(thongBaoMoi);

Giải thích:

  • Chúng ta dùng dấu backticks `` để bao quanh toàn bộ chuỗi.
  • Bên trong chuỗi, thay vì dùng +, chúng ta dùng ${ten}${tuoi}. JavaScript sẽ tự động lấy giá trị của biến tentuoi thay thế vào vị trí này.
  • Code trở nên ngắn gọndễ đọc hơn rất nhiều.

Không chỉ biến, bạn có thể nhúng cả biểu thức:

let gia = 100;
let giamGia = 0.1; // 10%

let tinNhan = `Giá sau giảm giá là ${gia * (1 - giamGia)} VNĐ.`;
console.log(tinNhan); // Output: Giá sau giảm giá là 90 VNĐ.

Giải thích:

  • Bên trong ${}, chúng ta đặt một biểu thức toán học: gia * (1 - giamGia).
  • JavaScript tính toán kết quả của biểu thức này (100 * 0.9 = 90) và chèn giá trị 90 vào chuỗi.
1.2. Chuỗi đa dòng (Multi-line Strings)

Một vấn đề đau đầu khác với chuỗi truyền thống là tạo chuỗi có nhiều dòng. Bạn phải dùng ký tự xuống dòng \n hoặc nối nhiều chuỗi lại với nhau.

// Cách truyền thống tạo chuỗi đa dòng
let diaChiCu = "Đường A, Số nhà B\n" +
               "Phường C, Quận D\n" +
               "Thành phố E.";
console.log(diaChiCu);

Với Template Literals, bạn chỉ việc gõ chuỗi trên nhiều dòng như bình thường!

// Sử dụng Template Literals tạo chuỗi đa dòng
let diaChiMoi = `Đường A, Số nhà B
Phường C, Quận D
Thành phố E.`;
console.log(diaChiMoi);

Giải thích:

  • JavaScript nhận diện và giữ nguyên các ký tự xuống dòng (Enter) mà bạn gõ bên trong cặp backticks.
  • Điều này cực kỳ hữu ích khi bạn cần tạo các đoạn văn bản dài, email, hoặc các cấu trúc HTML/CSS nhúng trong JavaScript.
1.3. Nhúng dấu ngoặc kép và dấu nháy đơn dễ dàng

Một lợi ích nhỏ nhưng tiện lợi của Template Literals là bạn không cần phải escape dấu nháy đơn (') hay dấu ngoặc kép (") nữa, miễn là chúng khác với ký tự bao quanh chuỗi (ở đây là backticks).

let cauNoi = `"Anh ấy nói: 'Xin chào!' một cách thân thiện."`; // Vẫn dùng nháy kép/đơn
console.log(cauNoi);

let cauNoiTL = `"Anh ấy nói: 'Xin chào!' một cách thân thiện."`; // Template Literals cũng làm được, không cần escape
console.log(cauNoiTL);

let viDuKhac = `Thật tuyệt khi dùng backticks \` trong chuỗi của bạn!`; // Chỉ cần escape backtick nếu muốn dùng nó trong chuỗi backtick
console.log(viDuKhac);

Template Literals không chỉ giúp code của bạn dễ đọc hơn mà còn giảm thiểu lỗi khi làm việc với chuỗi phức tạp. Chúng là một công cụ thiết yếu trong JavaScript hiện đại.

2. Modules: Tổ chức code JavaScript một cách chuyên nghiệp

Khi ứng dụng JavaScript của bạn lớn dần, việc đặt tất cả code vào một file duy nhất hoặc sử dụng các kỹ thuật cũ như IIFE (Immediately Invoked Function Expression) để mô phỏng module sẽ trở nên khó khănbất tiện. Các vấn đề như xung đột tên biến/hàm (global scope pollution) hay quản lý sự phụ thuộc giữa các phần code khác nhau sẽ xuất hiện.

Modules trong JavaScript (chuẩn ES Modules, ra mắt từ ES6) ra đời để giải quyết triệt để những vấn đề này. Chúng cho phép bạn:

  • Chia code thành các file nhỏ hơn, có ý nghĩa: Mỗi file (module) tập trung vào một nhiệm vụ cụ thể.
  • Tạo phạm vi riêng (scope) cho từng module: Các biến, hàm bên trong một module mặc định không truy cập được từ bên ngoài, tránh xung đột tên.
  • Công khai (export) những phần cần thiết: Bạn chỉ xuất ra ngoài những biến, hàm, lớp mà các module khác cần sử dụng.
  • Sử dụng (import) các thành phần từ module khác: Bạn nhập vào module hiện tại những gì đã được xuất từ module khác.
  • Quản lý phụ thuộc rõ ràng: Dễ dàng biết module nào phụ thuộc vào module nào.

Hãy xem cú pháp exportimport.

2.1. Export: Công khai những gì module cung cấp

Bạn sử dụng từ khóa export để "công khai" các biến, hàm, lớp, hằng số... từ module của mình ra bên ngoài.

Ví dụ: Tạo một file utils.js chứa các hàm tiện ích:

// utils.js
export const PI = 3.14159;

export function tinhTong(a, b) {
  return a + b;
}

export class Nguoi {
  constructor(ten) {
    this.ten = ten;
  }

  xinChao() {
    console.log(`Xin chào, tôi là ${this.ten}`);
  }
}

// Bạn cũng có thể export nhiều thứ cùng lúc ở cuối file
const hangSoKhac = 100;
function hamKhac() { /* ... */ }

export { hangSoKhac, hamKhac };

Giải thích:

  • Chúng ta dùng export trước các khai báo (const, function, class) để biến chúng thành "named exports" (các mục được xuất có tên).
  • Cú pháp export { item1, item2 } cho phép export một danh sách các mục đã được định nghĩa trước đó trong file.
2.2. Import: Sử dụng những gì module khác cung cấp

Bạn sử dụng từ khóa import để "nhập" các thành phần đã được export từ module khác vào module hiện tại.

Ví dụ: Sử dụng các tiện ích từ utils.js trong một file khác, ví dụ main.js:

// main.js
import { PI, tinhTong, Nguoi, hangSoKhac } from './utils.js';

console.log(PI); // Output: 3.14159

let ketQua = tinhTong(5, 7);
console.log(ketQua); // Output: 12

let teo = new Nguoi("Tèo");
teo.xinChao(); // Output: Xin chào, tôi là Tèo

console.log(hangSoKhac); // Output: 100

// Không thể truy cập hamKhac nếu không import nó
// hamKhac(); // Lỗi: hamKhac is not defined

Giải thích:

  • Cú pháp import { item1, item2 } from './path/to/module.js'; dùng để nhập các named exports.
  • Tên các mục trong dấu ngoặc nhọn {} phải trùng khớp với tên khi export.
  • './utils.js' là đường dẫn đến file module cần nhập. Đường dẫn có thể là tương đối (./, ../) hoặc tuyệt đối (với các module Node.js hoặc các thư viện được cấu hình đặc biệt).
2.3. Export Default: Một module, một "đại diện" chính

Ngoài named exports, mỗi module có thể có duy nhất một export mặc định (default export). Điều này thường dùng để xuất ra thứ được coi là "quan trọng nhất" hoặc là "chức năng chính" của module đó (ví dụ: một lớp React Component, một hàm tạo chính, một object cấu hình...).

Bạn sử dụng export default.

Ví dụ: Tạo file math.js với một hàm tính trung bình là default export:

// math.js
export const motNua = 0.5; // Named export

const tinhTrungBinh = (arr) => {
  if (arr.length === 0) return 0;
  const tong = arr.reduce((sum, num) => sum + num, 0);
  return tong / arr.length;
};

export default tinhTrungBinh; // Default export

Giải thích:

  • export default tinhTrungBinh; xuất hàm tinhTrungBinh làm default export. Lưu ý không cần dùng {}.
  • Bạn có thể export default một biến, hàm, lớp, object, array...
2.4. Import Default: Nhập đại diện chính của module

Khi import default export, bạn không cần dùng dấu ngoặc nhọn {} và có thể đặt cho nó bất kỳ tên nào bạn muốn.

Ví dụ: Sử dụng math.js trong main.js:

// main.js
import trungBinhCong from './math.js'; // Import default export và đặt tên là 'trungBinhCong'
import { motNua } from './math.js'; // Import named export

const soLieu = [10, 20, 30, 40];
const ketQuaTB = trungBinhCong(soLieu);
console.log(`Trung bình: ${ketQuaTB}`); // Output: Trung bình: 25

console.log(`Một nửa: ${motNua}`); // Output: Một nửa: 0.5

// Bạn có thể import cả default và named export cùng lúc
import defaultExport, { namedExport1, namedExport2 } from './module.js';

Giải thích:

  • import trungBinhCong from './math.js'; nhập default export từ math.js và gọi nó là trungBinhCong. Tên trungBinhCong ở đây hoàn toàn do bạn quyết định.
  • import { motNua } from './math.js'; vẫn nhập named export motNua như bình thường.
2.5. Import mọi thứ (Namespace Import)

Trong một số trường hợp, bạn muốn nhập tất cả những gì một module export (cả named và default) vào một object duy nhất. Bạn dùng cú pháp import * as tenAlias from './module.js'.

// main.js
import * as utilities from './utils.js';

console.log(utilities.PI); // Truy cập named export PI
console.log(utilities.tinhTong(2, 3)); // Truy cập named export tinhTong

// Default export (nếu có trong utils.js) sẽ nằm dưới key 'default' của object alias
// import * as mathModule from './math.js';
// console.log(mathModule.default([1, 2, 3])); // Truy cập default export
// console.log(mathModule.motNua); // Truy cập named export

Giải thích:

  • import * as utilities from './utils.js'; tạo một object utilities chứa tất cả các named exports từ utils.js dưới dạng các thuộc tính.
  • Cách này giúp tránh xung đột tên nếu bạn nhập nhiều thứ từ nhiều module khác nhau.
2.6. Dynamic Import (Nhập động)

Các import thông thường là static, nghĩa là chúng được xử lý khi code được tải lên (parse time). Đôi khi, bạn muốn tải một module chỉ khi cần thiết (ví dụ: khi người dùng click vào một nút, hoặc khi một điều kiện nào đó xảy ra). Đây gọi là dynamic import.

Cú pháp là import('./path/to/module.js'). Nó trả về một Promise.

// main.js
const nutLoadModule = document.getElementById('load-module');

nutLoadModule.addEventListener('click', async () => {
  try {
    // Dynamic import trả về một Promise
    const mathModule = await import('./math.js');

    const soLieu = [1, 2, 3, 4, 5];
    const avg = mathModule.default(soLieu); // Truy cập default export qua .default
    const half = mathModule.motNua;       // Truy cập named export

    console.log(`Kết quả trung bình sau khi load động: ${avg}`);
    console.log(`Một nửa từ module load động: ${half}`);

  } catch (error) {
    console.error("Lỗi khi tải module:", error);
  }
});

Giải thích:

  • Hàm import() được gọi bên trong một sự kiện click, nghĩa là module math.js chỉ được tải khi nút được click.
  • Chúng ta dùng await (trong một hàm async) hoặc .then() để xử lý Promise trả về.
  • Object mathModule chứa tất cả các exports của module. Default export nằm dưới key default, named exports là các thuộc tính khác.

Dynamic import rất hữu ích cho việc lazy loading (tải theo nhu cầu), giúp giảm thời gian tải ban đầu của ứng dụng.

2.7. Sử dụng Modules trong HTML

Để trình duyệt hiểu và xử lý cú pháp import/export, bạn cần thêm thuộc tính type="module" vào thẻ <script> của mình:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sử dụng Modules JS</title>
</head>
<body>
    <h1>Ví dụ về Modules</h1>
    <button id="load-module">Load Module Động</button>

    <!-- File script chính của bạn sử dụng modules -->
    <script type="module" src="./main.js"></script>

    <!-- Các script thông thường (không phải module) -->
    <!-- <script src="./other-script.js"></script> -->

</body>
</html>

Lưu ý quan trọng:

  • Các file .js sử dụng import hoặc export cần được đặt ở chế độ module bằng type="module".
  • Module chạy ở chế độ nghiêm ngặt (strict mode) theo mặc định.
  • Modules thường được tải defer theo mặc định (không chặn parse HTML), tương tự như <script defer>.
  • Đường dẫn trong import (ví dụ: ./utils.js) phải chính xác và phân biệt chữ hoa chữ thường trên một số hệ điều hành.
  • Module chỉ hoạt động khi chạy trên một máy chủ web (ví dụ: sử dụng Live Server trong VS Code, hoặc deploy lên một host). Mở file HTML trực tiếp bằng file:// có thể gặp lỗi liên quan đến CORS khi tải các module con.

Template Literals và Modules là hai bước tiến lớn trong sự phát triển của JavaScript, giúp code trở nên sạch sẽ hơn, dễ bảo trì hơncó cấu trúc tốt hơn. Nắm vững chúng là điều cần thiết để xây dựng các ứng dụng web hiện đại!

Chúc mừng bạn đã hoàn thành bài học này! Hãy thực hành viết code sử dụng Template Literals và Modules để làm quen với cú pháp và cảm nhận sự tiện lợi của chúng nhé!

Comments

There are no comments at the moment.